Bug 30327: Add options for sorting components

This patch adds two new sysprefs:
 ComponentSortField
 ComponentSortOrder

These allow the user to choose how components should be sorted when displaying on the details page
of a record, and the corresponding search for all components

This also updates our search from simple_search_compat to search_compat to allow for sorting options

Note:
Some sorting under ES is unclear - this is a separate issue to be invesitgated
Our Zebra index does not offer 'record number' sorting, I will file a bug for that

To test:
 1 - Enable UseControlNumber (or not)
 2 - Add some components to a record by control number or title depending on above
 3 - Enable  ShowComponentRecords  syspref
 4 - View the record that has components
 5 - Note they are not sorted
 6 - Apply patch, updatedatabase
 7 - reload record
 8 - Note components are sorted by title ascending
 9 - Try different values for ComponentSortField and ComponentSortOrder
10 - Confirm sorting changes with system preferences
11 - Repeat test on staff and opac, with ES and Zebra search engines

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
(cherry picked from commit de63c2abb1)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Nick Clemens 2022-06-01 14:40:49 +00:00 committed by Lucas Gass
parent 84999e1938
commit 488c1a71c9
9 changed files with 80 additions and 25 deletions

View file

@ -516,8 +516,8 @@ sub suggestions {
my $components = $self->get_marc_components();
Returns an array of MARCXML data, which are component parts of
this object (MARC21 773$w points to this)
Returns an array of search results data, which are component parts of
this object (MARC21 773 points to this)
=cut
@ -526,19 +526,19 @@ sub get_marc_components {
return [] if (C4::Context->preference('marcflavour') ne 'MARC21');
my $searchstr = $self->get_components_query;
my ( $searchstr, $sort ) = $self->get_components_query;
my $components;
if (defined($searchstr)) {
my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
my ( $error, $results, $total_hits );
my ( $error, $results, $facets );
eval {
( $error, $results, $total_hits ) = $searcher->simple_search_compat( $searchstr, 0, $max_results );
( $error, $results, $facets ) = $searcher->search_compat( $searchstr, undef, [$sort], ['biblioserver'], $max_results, 0, undef, undef, 'ccl', 0 );
};
if( $error || $@ ) {
$error //= q{};
$error .= $@ if $@;
warn "Warning from simple_search_compat: '$error'";
warn "Warning from search_compat: '$error'";
$self->add_message(
{
type => 'error',
@ -547,7 +547,7 @@ sub get_marc_components {
}
);
}
$components = $results if defined($results) && @$results;
$components = $results->{biblioserver}->{RECORDS} if defined($results) && $results->{biblioserver}->{hits};
}
return $components // [];
@ -565,6 +565,7 @@ sub get_components_query {
my $builder = Koha::SearchEngine::QueryBuilder->new(
{ index => $Koha::SearchEngine::BIBLIOS_INDEX } );
my $marc = $self->metadata->record;
my $sort = C4::Context->preference('ComponentSortField') . "_" . C4::Context->preference('ComponentSortOrder');
my $searchstr;
if ( C4::Context->preference('UseControlNumber') ) {
@ -597,8 +598,13 @@ sub get_components_query {
$cleaned_title = $builder->clean_search_term($cleaned_title);
$searchstr = "Host-item:($cleaned_title)";
}
my ($error, $query_str) = $builder->build_query_compat( undef, [$searchstr], undef, undef, [$sort], 0 );
if( $error ){
warn $error;
return;
}
return $searchstr;
return ($query_str, $sort);
}
=head3 subscriptions

View file

@ -222,7 +222,9 @@ if ( $showcomp eq 'both' || $showcomp eq 'staff' ) {
);
}
$template->param( ComponentParts => $parts );
$template->param( ComponentPartsQuery => $biblio->get_components_query );
my ( $comp_query, $comp_sort ) = $biblio->get_components_query;
my $cpq = $comp_query . "&sort_by=" . $comp_sort;
$template->param( ComponentPartsQuery => $cpq );
}
} else { # check if we should show analytics anyway
$show_analytics = 1 if $marc_record && @{$biblio->get_marc_components(1)}; # count matters here, results does not

View file

@ -0,0 +1,16 @@
use Modern::Perl;
return {
bug_number => "30327",
description => "Add ComponentsSortField and ComponentsSortOrder sysprefs",
up => sub {
my ($args) = @_;
my ($dbh, $out) = @$args{qw(dbh out)};
$dbh->do(q{
INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
('ComponentsSortField','title','call_number|pubdate|acqdate|title|author','Specify the default field used for sorting','Choice'),
('ComponentsSortOrder','asc','asc|dsc|az|za','Specify the default sort order','Choice')
});
say $out "Added ComponentsSortField and ComponentsSortOrder sysprefs";
},
};

View file

@ -144,6 +144,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
('CoceProviders', '', 'aws,gb,ol', 'Coce providers', 'multiple'),
('COinSinOPACResults','1','','If ON, use COinS in OPAC search results page. NOTE: this can slow down search response time significantly','YesNo'),
('CollapseFieldsPatronAddForm','',NULL,'Collapse these fields by default when adding a new patron. These fields can still be expanded.','Multiple'),
('ComponentsSortField','title','call_number|pubdate|acqdate|title|author','Specify the default field used for sorting','Choice'),
('ComponentsSortOrder','asc','asc|dsc|az|za','Specify the default sort order','Choice'),
('ConfirmFutureHolds','0','','Number of days for confirming future holds','Integer'),
('ConsiderOnSiteCheckoutsAsNormalCheckouts','1',NULL,'Consider on-site checkouts as normal checkouts','YesNo'),
('CreateAVFromCataloguing', '1', '', 'Ability to create authorized values from the cataloguing module', 'YesNo'),

View file

@ -280,6 +280,22 @@ Cataloging:
- pref: MaxComponentRecords
- "records will be displayed."
- "<br/> UNIMARC is not supported."
- By default, sort component results in the staff interface by
- pref: ComponentSortField
default: title
choices:
call_number: call number
pubdate: date of publication
acqdate: date added
title: title
author: author
- ','
- pref: ComponentSortOrder
choices:
asc: ascending.
dsc: descending.
az: from A to Z.
za: from Z to A.
Importing:
-
- When matching on ISBN with the record import tool,

View file

@ -707,7 +707,7 @@ Note that permanent location is a code, and location may be an authval.
[% END %]
</table>
[% IF ComponentParts.size == Koha.Preference('MaxComponentRecords')%]
<p>Only [% ComponentParts.size | html %] results are shown: <a href="/cgi-bin/koha/catalogue/search.pl?q=[% ComponentPartsQuery | uri %]"/>show all component parts</a></p>
<p>Only [% ComponentParts.size | html %] results are shown: <a href="/cgi-bin/koha/catalogue/search.pl?q=[% ComponentPartsQuery | url %]"/>show all component parts</a></p>
[% END %]
</div> <!-- /.content_set -->
</div> <!-- /#components -->

View file

@ -600,7 +600,7 @@
[% END %]
</table>
[% IF ComponentParts.size == Koha.Preference('MaxComponentRecords')%]
<p>Only [% ComponentParts.size | html %] results are shown: <a href="/cgi-bin/koha/opac-search.pl?q=[% ComponentPartsQuery | uri %]"/>show all component parts</a></p>
<p>Only [% ComponentParts.size | html %] results are shown: <a href="/cgi-bin/koha/opac-search.pl?q=[% ComponentPartsQuery | url %]"/>show all component parts</a></p>
[% END %]
</div>
</div>

View file

@ -650,7 +650,9 @@ if ( $showcomp eq 'both' || $showcomp eq 'opac' ) {
);
}
$template->param( ComponentParts => $parts );
$template->param( ComponentPartsQuery => $biblio->get_components_query );
my ( $comp_query, $comp_sort ) = $biblio->get_components_query;
my $cpq = $comp_query . "&sort_by=" . $comp_sort;
$template->param( ComponentPartsQuery => $cpq );
}
} else { # check if we should show analytics anyway
$show_analytics = 1 if @{$biblio->get_marc_components(1)}; # count matters here, results does not

View file

@ -518,7 +518,7 @@ subtest 'get_marc_components() tests' => sub {
my $host_biblio = Koha::Biblios->find($host_bibnum);
t::lib::Mocks::mock_preference( 'SearchEngine', 'Zebra' );
my $search_mod = Test::MockModule->new( 'Koha::SearchEngine::Zebra::Search' );
$search_mod->mock( 'simple_search_compat', \&search_component_record2 );
$search_mod->mock( 'search_compat', \&search_component_record2 );
my $components = $host_biblio->get_marc_components;
is( ref($components), 'ARRAY', 'Return type is correct' );
@ -529,8 +529,8 @@ subtest 'get_marc_components() tests' => sub {
'->get_marc_components returns an empty ARRAY'
);
$search_mod->unmock( 'simple_search_compat');
$search_mod->mock( 'simple_search_compat', \&search_component_record1 );
$search_mod->unmock( 'search_compat');
$search_mod->mock( 'search_compat', \&search_component_record1 );
my $component_record = component_record1()->as_xml();
is_deeply(
@ -538,13 +538,13 @@ subtest 'get_marc_components() tests' => sub {
[$component_record],
'->get_marc_components returns the related component part record'
);
$search_mod->unmock( 'simple_search_compat');
$search_mod->unmock( 'search_compat');
$search_mod->mock( 'simple_search_compat',
$search_mod->mock( 'search_compat',
sub { Koha::Exception->throw("error searching analytics") }
);
warning_like { $components = $host_biblio->get_marc_components }
qr{Warning from simple_search_compat: .* 'error searching analytics'};
qr{Warning from search_compat: .* 'error searching analytics'};
is_deeply(
$host_biblio->object_messages,
@ -556,35 +556,46 @@ subtest 'get_marc_components() tests' => sub {
}
]
);
$search_mod->unmock( 'simple_search_compat');
$search_mod->unmock( 'search_compat');
$schema->storage->txn_rollback;
};
subtest 'get_components_query' => sub {
plan tests => 3;
plan tests => 6;
my $biblio = $builder->build_sample_biblio();
my $biblionumber = $biblio->biblionumber;
my $record = $biblio->metadata->record;
t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
is($biblio->get_components_query, "Host-item:(Some boring read)", "UseControlNumber disabled");
t::lib::Mocks::mock_preference( 'ComponentSortField', 'author' );
t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'za' );
my ( $comp_q, $comp_s ) = $biblio->get_components_query;
is($comp_q, "Host-item:(Some boring read)",, "UseControlNumber disabled");
is($comp_s, "author_za", "UseControlNumber disabled sort is correct");
t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'az' );
my $marc_001_field = MARC::Field->new('001', $biblionumber);
$record->append_fields($marc_001_field);
C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
$biblio = Koha::Biblios->find( $biblio->biblionumber);
is($biblio->get_components_query, "(rcn:$biblionumber AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled without MarcOrgCode");
( $comp_q, $comp_s ) = $biblio->get_components_query;
is($comp_q, "(rcn:$biblionumber AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled without MarcOrgCode");
is($comp_s, "author_az", "UseControlNumber enabled without MarcOrgCode sort is correct");
my $marc_003_field = MARC::Field->new('003', 'OSt');
$record->append_fields($marc_003_field);
C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
$biblio = Koha::Biblios->find( $biblio->biblionumber);
is($biblio->get_components_query, "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled with MarcOrgCode");
t::lib::Mocks::mock_preference( 'ComponentSortField', 'title' );
t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'asc' );
( $comp_q, $comp_s ) = $biblio->get_components_query;
is($comp_q, "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled with MarcOrgCode");
is($comp_s, "title_asc", "UseControlNumber enabled with MarcOrgCode sort if correct");
};
subtest 'orders() and active_orders() tests' => sub {
@ -1014,12 +1025,12 @@ sub component_record1 {
}
sub search_component_record1 {
my @results = ( component_record1()->as_xml() );
return ( undef, \@results, 1 );
return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
}
sub search_component_record2 {
my @results;
return ( undef, \@results, 0 );
return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
}
sub host_record {