From f5271d1a743d672cce2bfe04fc237643d1bd093d Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Mon, 18 Apr 2022 14:51:36 +0000 Subject: [PATCH] Bug 17170: Add the ability to create 'saved searches' for use as filters when searching the catalog This patch alters the code in the ES QueryBuilder. Reflecting how things are handled in build_query_compat we clean the query, but not the limits. In Zebra we simply recursivly call buildQuery, but the ES query structure This patchset adds a new ability to save searches on the staff client, and display them in the results page on staff or opac as a new filter. New filters can be added from the results page after a search, and there is an admin page for updating deleting and renaming filters There is a new permission to control management of these filters New filters can be added that are not displayed along with facets, this allows for building custom links using these filters to keep URLs shorter Due to bug 30528 testing in ES is recommended To test: 1 - Apply patches and update database and restart all 2 - Enable new system preference 'SavedSearchFilters' 3 - As superlibrarian perform a search in staff client, something broad like 'a' 4 - Note new 'Save search as filter' link on results page 5 - Click it, save search as new filter, check 'Staff client' visibility 6 - Perform another search 7 - Note the filter now appears above facets 8 - Click to it filter results 9 - Note results are limited by the new filter, and it is checked in the facets 10 - Confirm click the [x] removes the filter 11 - Go to administration->search filters 12 - Confirm the filter appears 13 - Edit and mark as OPAC visible 14 - Test OPAC to ensure it shows and can be applied/removed 15 - Copy URL with filter applied 16 - In adminsitration mark filter as not visible on staff or opac 17 - Confirm link above still works 18 - Create a new staff with catalogue and search filters permission 19 - Ensure they can access/save filters 20 - Remove filter permission and ensure they cannot 21 - Disable system preference 22 - Confirm links to search filters page are removed from admin home and admin sidebar 23 - Confirm filters do not appear on results and cannot be created 24 - Enable pref 25 - Create a filter 26 - From search filters page, click 'Edit search' 27 - Confirm you are taken to advanced search page letting you know which filter you are editing 28 - Confirm you can change searhc options and save 29 - Confirm you can perform the search from this page Sponsored-by: Sponsored by: Round Rock Public Library [https://www.roundrocktexas.gov/departments/library/] Signed-off-by: Michal Urban Signed-off-by: Martin Renvoize Signed-off-by: Tomas Cohen Arazi --- C4/Search.pm | 7 +++++-- .../Elasticsearch/QueryBuilder.pm | 11 +++++++--- Koha/SearchFilter.pm | 10 ++++----- .../SearchEngine/Elasticsearch/QueryBuilder.t | 21 +++++++++++++++---- .../Koha/SearchEngine/Zebra/QueryBuilder.t | 21 +++++++++++++++---- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/C4/Search.pm b/C4/Search.pm index c396414242..e6ad8b6ff3 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -1301,12 +1301,15 @@ sub buildQuery { $limit .= "$this_limit"; $limit_desc .= " $this_limit"; } elsif ( $this_limit =~ '^search_filter:' ) { + # Here we will get the query as a string, append to the limits, and pass through buildQuery + # again to clean the terms and handle nested filters $limit_cgi .= "&limit=" . uri_escape_utf8($this_limit); my ($filter_id) = ( $this_limit =~ /^search_filter:(.*)$/ ); my $search_filter = Koha::SearchFilters->find( $filter_id ); next unless $search_filter; - my $expanded = $search_filter->expand_filter; - my ( $error, undef, undef, undef, undef, $fixed_limit, undef, undef, undef ) = buildQuery ( undef, undef, undef, $expanded, undef, undef, $lang); + my ($expanded_lim, $query_lim) = $search_filter->expand_filter; + push @$expanded_lim, $query_lim; + my ( $error, undef, undef, undef, undef, $fixed_limit, undef, undef, undef ) = buildQuery ( undef, undef, undef, $expanded_lim, undef, undef, $lang); $limit .= " and " if $limit || $query; $limit .= "$fixed_limit"; $limit_desc .= " $limit"; diff --git a/Koha/SearchEngine/Elasticsearch/QueryBuilder.pm b/Koha/SearchEngine/Elasticsearch/QueryBuilder.pm index 554c447601..53042b4303 100644 --- a/Koha/SearchEngine/Elasticsearch/QueryBuilder.pm +++ b/Koha/SearchEngine/Elasticsearch/QueryBuilder.pm @@ -1070,13 +1070,18 @@ sub _fix_limit_special_cases { push @new_lim, "date-of-publication:[$start TO $end]"; } elsif( $l =~ /^search_filter:/ ){ + # Here we are going to get the query as a string, clean it, and take care of the part of the limit + # Calling build_query_compat here is avoided because we generate more complex query structures my ($filter_id) = ( $l =~ /^search_filter:(.*)$/ ); my $search_filter = Koha::SearchFilters->find( $filter_id ); next unless $search_filter; - my $expanded = $search_filter->expand_filter; - foreach my $e ( @{$self->_fix_limit_special_cases($expanded)} ) { - push @new_lim, $self->clean_search_term( $e ); + my ($expanded_lim,$query_lim) = $search_filter->expand_filter; + # In the case of nested filters we need to expand them all + foreach my $el ( @{$self->_fix_limit_special_cases($expanded_lim)} ){ + push @new_lim, $el; } + # We need to clean the query part as we have built a string from the original search + push @new_lim, $self->clean_search_term( $query_lim ); } elsif ( $l =~ /^yr,st-numeric[=:]/ ) { my ($date) = ( $l =~ /^yr,st-numeric[=:](.*)$/ ); diff --git a/Koha/SearchFilter.pm b/Koha/SearchFilter.pm index 493e1fc1ee..547e89a448 100644 --- a/Koha/SearchFilter.pm +++ b/Koha/SearchFilter.pm @@ -43,10 +43,10 @@ sub to_api_mapping { =head3 expand_filter - my $expanded_filter = $filter->expand_filter; + my ($expanded_limit, $query_limit) = $filter->expand_filter; - Returns the filter as an arrayref of limit queries suitable to - be passed to QueryBuilder + Returns the filter as an arrayref of limit queries, and the query parts combined + into a string suitable to be passed to QueryBuilder =cut @@ -74,9 +74,7 @@ sub expand_filter { $query_limit .= $limit; } - push @$limits, "(".$query_limit.")" if $query_limit; - - return $limits; + return ($limits, $query_limit); } =head2 Internal methods diff --git a/t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t b/t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t index 8488af5f56..561bcbfb1e 100755 --- a/t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t +++ b/t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t @@ -785,7 +785,7 @@ subtest 'build_query_compat() SearchLimitLibrary tests' => sub { }; subtest "Handle search filters" => sub { - plan tests => 4; + plan tests => 7; my $qb; @@ -797,15 +797,28 @@ subtest "Handle search filters" => sub { my $filter = Koha::SearchFilter->new({ name => "test", query => q|{"operands":["cat","bat","rat"],"indexes":["kw","ti","au"],"operators":["AND","OR"]}|, - limits => q|{"limits":["mc-itype,phr:BK","available"]}|, + limits => q|{"limits":["mc-itype,phr:BK","mc-itype,phr:MU","available"]}|, })->store; my $filter_id = $filter->id; my ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] ); - is( $limit,q{(itype:("BK")) AND (onloan:false) AND (((cat) AND title:(bat) OR author:(rat)))},"Limit correctly formed"); + is( $limit,q{(onloan:false) AND ((cat) AND title:(bat) OR author:(rat)) AND itype:(("BK") OR ("MU"))},"Limit correctly formed"); is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded"); - is( $limit_desc,q{(itype:("BK")) AND (onloan:false) AND (((cat) AND title:(bat) OR author:(rat)))},"Limit description is correctly expanded"); + is( $limit_desc,q{(onloan:false) AND ((cat) AND title:(bat) OR author:(rat)) AND itype:(("BK") OR ("MU"))},"Limit description is correctly expanded"); + + $filter = Koha::SearchFilter->new({ + name => "test", + query => q|{"operands":["su:biography"],"indexes":[],"operators":[]}|, + limits => q|{"limits":[]}|, + })->store; + $filter_id = $filter->id; + + ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] ); + + is( $limit,q{(subject:biography)},"Limit correctly formed for ccl type query"); + is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded"); + is( $limit_desc,q{(subject:biography)},"Limit description is correctly handled for ccl type query"); }; diff --git a/t/db_dependent/Koha/SearchEngine/Zebra/QueryBuilder.t b/t/db_dependent/Koha/SearchEngine/Zebra/QueryBuilder.t index 2653e45acd..5a8fdb8f50 100755 --- a/t/db_dependent/Koha/SearchEngine/Zebra/QueryBuilder.t +++ b/t/db_dependent/Koha/SearchEngine/Zebra/QueryBuilder.t @@ -98,7 +98,7 @@ subtest 'build_query_compat() SearchLimitLibrary tests' => sub { }; subtest "Handle search filters" => sub { - plan tests => 4; + plan tests => 7; my $qb; @@ -110,14 +110,27 @@ subtest "Handle search filters" => sub { my $filter = Koha::SearchFilter->new({ name => "test", query => q|{"operands":["cat","bat","rat"],"indexes":["kw","ti","au"],"operators":["AND","OR"]}|, - limits => q|{"limits":["mc-itype,phr:BK","available"]}|, + limits => q|{"limits":["mc-itype,phr:BK","mc-itype,phr:MU","available"]}|, })->store; my $filter_id = $filter->id; my ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] ); - is( $limit,q{(kw=(cat) AND ti=(bat) OR au=(rat)) and (mc-itype,phr=BK) and (( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) ))},"Limit correctly formed"); + is( $limit,q{kw=(cat) AND ti=(bat) OR au=(rat) and (mc-itype,phr=BK or mc-itype,phr=MU) and (( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) ))},"Limit correctly formed"); is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded"); - is( $limit_desc,q{(kw=(cat) AND ti=(bat) OR au=(rat)) and (mc-itype,phr=BK) and (( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) ))},"Limit description is correctly expanded"); + is( $limit_desc,q{kw=(cat) AND ti=(bat) OR au=(rat) and (mc-itype,phr=BK or mc-itype,phr=MU) and (( (allrecords,AlwaysMatches='') and (not-onloan-count,st-numeric >= 1) and (lost,st-numeric=0) ))},"Limit description is correctly expanded"); + + $filter = Koha::SearchFilter->new({ + name => "test", + query => q|{"operands":["su:biography"],"indexes":[],"operators":[]}|, + limits => q|{"limits":[]}|, + })->store; + $filter_id = $filter->id; + + ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] ); + + is( $limit,q{(su=biography)},"Limit correctly formed for ccl type query"); + is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded"); + is( $limit_desc,q{(su=biography)},"Limit description is correctly handled for ccl type query"); }; -- 2.39.5