diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcher.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcher.java index a4512b0a0..6643f926d 100644 --- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcher.java +++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcher.java @@ -50,9 +50,11 @@ public SearchResults search( int page, int rows, List facetFilters, - String catalogueKey + String catalogueKey, + String sortField, + SolrQuery.ORDER sortOrder ) { - val basicResults= super.search( + val basicResults = super.search( endpoint, user, term, @@ -61,7 +63,9 @@ public SearchResults search( page, rows, facetFilters, - catalogueKey + catalogueKey, + sortField, + sortOrder ); val relatedSearches = relatedSearches(endpoint, term); log.debug(relatedSearches.toString()); diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchController.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchController.java index f1962d746..e9f40b2c3 100644 --- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchController.java +++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchController.java @@ -5,6 +5,7 @@ import lombok.ToString; import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.apache.solr.client.solrj.SolrQuery; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -28,6 +29,8 @@ public class SearchController { public static final String PAGE_QUERY_PARAM = "page"; public static final String ROWS_QUERY_PARAM = "rows"; public static final String FACET_QUERY_PARAM = "facet"; + public static final String SORT_FIELD_PARAM = "sortField"; + public static final String SORT_ORDER_PARAM = "order"; public static final int PAGE_DEFAULT = Integer.parseInt(PAGE_DEFAULT_STRING); public static final int ROWS_DEFAULT = Integer.parseInt(ROWS_DEFAULT_STRING); @@ -78,6 +81,10 @@ public SearchResults search( int rows, @RequestParam(value=FACET_QUERY_PARAM, defaultValue = "") List facetFilters, + @RequestParam(value=SORT_FIELD_PARAM, required = false) + String sortField, + @RequestParam(value=SORT_ORDER_PARAM, defaultValue = "asc") + String sortOrder, HttpServletRequest request ) { return searcher.search( @@ -89,7 +96,9 @@ public SearchResults search( page, rows, facetFilters, - catalogueKey + catalogueKey, + sortField, + "desc".equals(sortOrder) ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc ); } } diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchQuery.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchQuery.java index a0d8bc611..11f90029c 100644 --- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchQuery.java +++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SearchQuery.java @@ -27,6 +27,16 @@ public class SearchQuery { public static final String DEFAULT_SEARCH_TERM = "*"; private static final String RANDOM_DYNAMIC_FIELD_NAME = "random"; + // Fields which need to have _raw appended before they can be used + // as a Solr sort option. Keep in sync with managed-schema. + private static final List RAW_SORT_FIELDS = List.of( + "title", + "description", + "lineage", + "objectives", + "infrastructureCapabilities" + ); + String endpoint; CatalogueUser user; @NotNull String term; @@ -38,6 +48,8 @@ public class SearchQuery { GroupStore groupStore; Catalogue catalogue; List facets; + String sortField; + SolrQuery.ORDER sortOrder; public SearchQuery( String endpoint, @@ -50,7 +62,9 @@ public SearchQuery( List facetFilters, GroupStore groupStore, Catalogue catalogue, - List facets + List facets, + String sortField, + SolrQuery.ORDER sortOrder ) { this.endpoint = endpoint; this.user = user; @@ -63,6 +77,8 @@ public SearchQuery( this.groupStore = groupStore; this.facets = facets; this.catalogue = catalogue; + this.sortField = sortField; + this.sortOrder = sortOrder; } public SolrQuery build(){ @@ -103,7 +119,9 @@ public SearchQuery withPage(int newPage) { facetFilters, groupStore, catalogue, - facets + facets, + sortField, + sortOrder ); } else { @@ -130,7 +148,9 @@ public SearchQuery withBbox(String newBbox) { facetFilters, groupStore, catalogue, - facets + facets, + sortField, + sortOrder ); } else { @@ -158,7 +178,9 @@ public SearchQuery withSpatialOperation(SpatialOperation newSpatialOperation) { facetFilters, groupStore, catalogue, - facets + facets, + sortField, + sortOrder ); } else { @@ -192,7 +214,9 @@ public SearchQuery withFacetFilter(FacetFilter filter) { newFacetFilters, groupStore, catalogue, - facets + facets, + sortField, + sortOrder ); } else { @@ -223,7 +247,9 @@ public SearchQuery withoutFacetFilter(FacetFilter filter) { newFacetFilters, groupStore, catalogue, - facets + facets, + sortField, + sortOrder ); } else { @@ -267,6 +293,13 @@ public String toUrl() { facetFilters.forEach((f)-> builder.queryParam(FACET_QUERY_PARAM, f.asURIContent())); } + if(sortField != null) { + builder.queryParam(SORT_FIELD_PARAM, sortField); + if (sortOrder != null) { + builder.queryParam(SORT_ORDER_PARAM, sortOrder); + } + } + return builder.build().toUriString(); } @@ -342,8 +375,11 @@ private void setCatalogueFilter(SolrQuery query) { ); } - private void setSortOrder(SolrQuery query){ - if(DEFAULT_SEARCH_TERM.equals(term)){ + private void setSortOrder(SolrQuery query) { + if (sortField != null) { + final String maybeRawSuffix = RAW_SORT_FIELDS.contains(sortField) ? "_raw" : ""; + query.setSort(sortField + maybeRawSuffix, sortOrder); + } else if (DEFAULT_SEARCH_TERM.equals(term)) { query.setSort(getRandomFieldName(), SolrQuery.ORDER.asc); } } diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/Searcher.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/Searcher.java index 5e7b267d5..0f3a2ab6c 100644 --- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/Searcher.java +++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/Searcher.java @@ -1,6 +1,7 @@ package uk.ac.ceh.gateway.catalogue.search; import uk.ac.ceh.gateway.catalogue.model.CatalogueUser; +import org.apache.solr.client.solrj.SolrQuery; import java.util.List; @@ -14,6 +15,8 @@ SearchResults search( int page, int rows, List facetFilters, - String catalogueKey + String catalogueKey, + String sortField, + SolrQuery.ORDER sortOrder ); } diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcher.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcher.java index bb893ca9a..b0559a4eb 100644 --- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcher.java +++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcher.java @@ -3,6 +3,7 @@ import lombok.SneakyThrows; import lombok.val; import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import uk.ac.ceh.components.userstore.GroupStore; @@ -45,7 +46,9 @@ public SearchResults search( int page, int rows, List facetFilters, - String catalogueKey + String catalogueKey, + String sortField, + SolrQuery.ORDER sortOrder ) { val catalogue = catalogueService.retrieve(catalogueKey); @@ -60,7 +63,9 @@ public SearchResults search( facetFilters, groupStore, catalogue, - facetFactory.newInstances(catalogue.getFacetKeys()) + facetFactory.newInstances(catalogue.getFacetKeys()), + sortField, + sortOrder ); val response = solrClient.query( "documents", diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcherTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcherTest.java index fd0bd4864..8e2cfc141 100644 --- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcherTest.java +++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/EnhancedSolrSearcherTest.java @@ -4,6 +4,7 @@ import lombok.val; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.SolrQuery.ORDER; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.params.SolrParams; import org.junit.jupiter.api.BeforeEach; @@ -44,6 +45,8 @@ class EnhancedSolrSearcherTest { private final int rows = 20; private final List facetFilters = List.of(new FacetFilter("filter|test")); private final String catalogueKey = "green"; + private final String sortField = "publishedDate"; + private final ORDER sortOrder = ORDER.desc; @BeforeEach void setup() { @@ -75,7 +78,9 @@ void search() { page, rows, facetFilters, - catalogueKey + catalogueKey, + sortField, + sortOrder ); //then diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchControllerTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchControllerTest.java index 1f0d0946c..e3c4321c4 100644 --- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchControllerTest.java +++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchControllerTest.java @@ -145,6 +145,8 @@ private void givenSearchResults() { anyInt(), anyInt(), any(), + any(), + any(), any() )).willReturn(searchResults); diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchQueryTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchQueryTest.java index a02f9e7ef..a879421f2 100644 --- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchQueryTest.java +++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchQueryTest.java @@ -34,6 +34,8 @@ public class SearchQueryTest { static final List DEFAULT_FACETS = FACET_FACTORY.newInstances( Arrays.asList("resourceType","licence") ); + private static final String sortField = "publicationDate"; + private static final SolrQuery.ORDER sortOrder = SolrQuery.ORDER.desc; @Test public void queryHasCatalogueAsViewFilter() { @@ -56,7 +58,9 @@ public void queryHasCatalogueAsViewFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -89,7 +93,9 @@ public void loggedInUserHasUsernameAsViewFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -123,7 +129,9 @@ public void loggedInUserWithGroupsHasUsernameAndGroupsAsViewFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -157,7 +165,9 @@ public void publisherDoesNotHaveViewFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -203,7 +213,9 @@ public void buildQueryWithNoExtraParameters() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + null, + null ); //When SolrQuery solrQuery = query.build(); @@ -242,7 +254,9 @@ public void buildQueryOnSecondPage() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -275,7 +289,9 @@ public void buildQueryWithSimpleTerm() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + null, + null ); //When SolrQuery solrQuery = query.build(); @@ -307,7 +323,9 @@ public void buildQueryWithDefaultTermAndFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When SolrQuery solrQuery = query.build(); @@ -340,7 +358,9 @@ public void noExceptionThrownWhenBBoxIsValid() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -373,7 +393,9 @@ public void canSetIntersectBBox() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -404,7 +426,9 @@ public void checkThatWithFacetReturnsToFirstPage() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -437,7 +461,9 @@ public void checkThatWithoutFacetReturnsToFirstPage() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -469,7 +495,9 @@ public void checkThatWithFacetFilterAddsNewFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -501,7 +529,9 @@ public void checkThatWithoutFacetFilterRemovesFilter() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -533,7 +563,9 @@ public void checkThatContainsFilterDelegatesToList() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); FacetFilter filter = new FacetFilter("hey", "lo"); @@ -566,7 +598,9 @@ public void checkThatCompleteUrlIsGenerated() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -603,7 +637,9 @@ public void checkUrlIsGenerated() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + null, + null ); //When @@ -634,7 +670,9 @@ public void checkThatDefaultQueryDoesNotContainQueryString() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + null, + null ); //When @@ -667,7 +705,9 @@ public void changeInBBoxFilterReturnsANewSearchQuery() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -698,7 +738,9 @@ public void sameBBoxReturnsSameSearchQuery() { .contactUrl("") .logo("") .build(), - DEFAULT_FACETS + DEFAULT_FACETS, + sortField, + sortOrder ); //When @@ -735,7 +777,9 @@ public void impFacetsConfigured() { DEFAULT_FILTERS, groupStore, catalogue, - FACET_FACTORY.newInstances(catalogue.getFacetKeys()) + FACET_FACTORY.newInstances(catalogue.getFacetKeys()), + sortField, + sortOrder ); //When @@ -755,4 +799,38 @@ public void impFacetsConfigured() { assertThat(actual.contains("resourceType"), is(true)); } + + @Test + public void checkSortParameters() { + //Given + SearchQuery interestingQuery = new SearchQuery( + "http://my.endpo.int", + CatalogueUser.PUBLIC_USER, + "My Search Term", + "1,2,3,4", + SpatialOperation.ISWITHIN, + 24, + 30, + List.of(new FacetFilter("licence", "b")), + groupStore, + Catalogue + .builder() + .id("eidc") + .title("Environmental Information Data Centre") + .url("https://eidc-catalogue.ceh.ac.uk") + .contactUrl("") + .logo("") + .build(), + DEFAULT_FACETS, + sortField, + sortOrder + ); + + //When + String url = interestingQuery.toUrl(); + + //Then + assertThat("sort field parameter should be present", url, containsString("sortField=publicationDate")); + assertThat("sort order parameter should be present", url, containsString("order=desc")); + } } diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchResultsTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchResultsTest.java index 1d789a440..bd47e25f2 100644 --- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchResultsTest.java +++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SearchResultsTest.java @@ -46,7 +46,9 @@ public void facetResultsArePresent() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -81,7 +83,9 @@ public void simpleSearchResults() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -124,7 +128,9 @@ public void checkThatPrevPageIsPresentOnSecondPage() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -158,7 +164,9 @@ public void checkThatPrevPageIsNotShownOnFirstPage() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -192,7 +200,9 @@ public void checkThatNoNextPageIsShownWhenOnLastPage() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -228,7 +238,9 @@ public void checkNextPageIsShownWhenThereAreMoreResults() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); @@ -265,7 +277,9 @@ public void checkThatNoWithoutBoundingBoxUrlIsGeneratedIfNotFilteringWithBoundin .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); //When @@ -298,7 +312,9 @@ public void checkThatWithoutBBoxUrlIsGeneratedWhenBBoxIsApplied() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); //When @@ -332,7 +348,9 @@ public void checkThatIsWithinUrlIsPresentWhenUsingIntersectsOperation() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); @@ -367,7 +385,9 @@ public void checkThatIsWithinUrlIsntPresentWhenUsingIsWithinOperation() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); @@ -401,7 +421,9 @@ public void checkThatIntersectingUrlIsPresentWhenUsingIsWithinOperation() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); @@ -436,7 +458,9 @@ public void checkThatIntersectingUrlIsNotPresentWhenUsingIntersectingOperation() .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); @@ -471,7 +495,9 @@ public void checkThatSwitchingSpatialOperationJumpsToPageOne() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); //When @@ -502,7 +528,9 @@ public void checkThatRelatedSearchesArePresent() { .contactUrl("") .logo("") .build(), - SearchQueryTest.DEFAULT_FACETS + SearchQueryTest.DEFAULT_FACETS, + null, + null ); QueryResponse response = mock(QueryResponse.class); diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcherTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcherTest.java index 76d44fb4e..883cf00ca 100644 --- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcherTest.java +++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/search/SolrSearcherTest.java @@ -2,6 +2,7 @@ import lombok.SneakyThrows; import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery.ORDER; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.params.SolrParams; @@ -42,6 +43,8 @@ class SolrSearcherTest { private final int rows = 20; private final List facetFilters = List.of(new FacetFilter("filter|test")); private final String catalogueKey = "green"; + private final String sortField = "publicationDate"; + private final ORDER sortOrder = ORDER.desc; @BeforeEach void setup() { @@ -70,7 +73,9 @@ void search() { page, rows, facetFilters, - catalogueKey + catalogueKey, + sortField, + sortOrder ); //then diff --git a/solr/documents/conf/managed-schema b/solr/documents/conf/managed-schema index 7fabcad72..f9b92fefe 100644 --- a/solr/documents/conf/managed-schema +++ b/solr/documents/conf/managed-schema @@ -155,6 +155,22 @@ + + + + + + + + + + + +