From c464485b080ebea38993dc742316932d184303d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1roly=20Erd=C5=91s?= <2183410+ke4@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:49:38 +0000 Subject: [PATCH] Refactor organism part endpoint (#352) * Pull and update latest submodule changes * Add organism part and cell type search to analytics search DAO * Clean variable naming * Add cell types as an input parameter to OrganismPartSearchDao * Add cell types as an input parameter to OrganismPartSearchService * clean code in JsonGeneSearchController * Add cell type query parameter to the JsonGeneSearchController - WIP * Rename test method name and remove unneeded test case * Clean the code that gets the matching gene ids * Update submodules * Update atlas-web-core to the latest version --- .../search/JsonGeneSearchController.java | 33 +++--- .../search/analytics/AnalyticsSearchDao.java | 85 ++++++++++++++ .../organismpart/OrganismPartSearchDao.java | 23 ++-- .../OrganismPartSearchService.java | 4 +- .../ebi/atlas/solr/SingleCellSolrUtils.java | 44 +++++++ .../search/JsonGeneSearchControllerIT.java | 25 ++-- .../analytics/AnalyticsSearchDaoIT.java | 110 +++++++++++++++--- .../organismpart/OrganismPartSearchDaoIT.java | 53 +++++++-- .../OrganismPartSearchServiceTest.java | 61 +++++++--- atlas-web-core | 2 +- 10 files changed, 357 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/JsonGeneSearchController.java b/app/src/main/java/uk/ac/ebi/atlas/search/JsonGeneSearchController.java index 170ad994c..b96eb1a45 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/JsonGeneSearchController.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/JsonGeneSearchController.java @@ -20,6 +20,7 @@ import uk.ac.ebi.atlas.search.analytics.AnalyticsSearchService; import uk.ac.ebi.atlas.search.geneids.GeneIdSearchService; import uk.ac.ebi.atlas.search.geneids.QueryParsingException; +import uk.ac.ebi.atlas.search.organismpart.OrganismPartSearchService; import uk.ac.ebi.atlas.search.species.SpeciesSearchService; import uk.ac.ebi.atlas.trader.ExperimentTrader; import uk.ac.ebi.atlas.utils.StringUtil; @@ -47,6 +48,7 @@ public class JsonGeneSearchController extends JsonExceptionHandlingController { private final ExperimentAttributesService experimentAttributesService; private final AnalyticsSearchService analyticsSearchService; + private final OrganismPartSearchService organismPartSearchService; private final SpeciesSearchService speciesSearchService; @GetMapping(value = "/json/search", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @@ -129,10 +131,11 @@ public String search(@RequestParam MultiValueMap requestParams) // If the query term matches a single Ensembl ID different to the query term, we return it in the response. // The most common case is a non-Ensembl gene identifier (e.g. Entrez, MGI, ...). - var matchingGeneIds = - (geneIds.get().size() == 1 && !geneIds.get().iterator().next().equals(geneQuery.queryTerm())) ? - "(" + String.join(", ", geneIds.get()) + ")" : - ""; + var matchingGeneIds = geneIds.filter( + strings -> strings.size() == 1 + && !strings.iterator().next().equals(geneQuery.queryTerm())) + .map(strings -> "(" + String.join(", ", strings) + ")") + .orElse(""); var json = GSON.toJson( ImmutableMap.of( @@ -177,12 +180,10 @@ public String searchForGene(@RequestParam MultiValueMap requestP expressedGeneIdEntries.stream() // TODO Measure in production if parallelising the stream results in any speedup // (the more experiments we have the better). BEWARE: just adding parallel() throws! (?) - .flatMap(entry -> entry.getValue().entrySet().stream().map(exp2cells -> { + .flatMap(entry -> entry.getValue().keySet().stream().map(experimentAccession -> { // Inside this map-within-a-flatMap we unfold expressedGeneIdEntries to triplets of... var geneId = entry.getKey(); - var experimentAccession = exp2cells.getKey(); - var cellIds = exp2cells.getValue(); var experimentAttributes = ImmutableMap.builder().putAll( @@ -205,10 +206,11 @@ public String searchForGene(@RequestParam MultiValueMap requestP })).collect(toImmutableList()); - var matchingGeneIds = ""; - if (geneIds.get().size() == 1 && !geneIds.get().iterator().next().equals(geneQuery.queryTerm())) { - matchingGeneIds = "(" + String.join(", ", geneIds.get()) + ")"; - } + var matchingGeneIds = geneIds.filter( + strings -> strings.size() == 1 + && !strings.iterator().next().equals(geneQuery.queryTerm())) + .map(strings -> "(" + String.join(", ", strings) + ")") + .orElse(""); return GSON.toJson( ImmutableMap.of( @@ -236,7 +238,7 @@ public Boolean isMarkerGene(@RequestParam MultiValueMap requestP .map(Map.Entry::getKey) .collect(toImmutableSet())); - return markerGeneFacets != null && markerGeneFacets.size() > 0; + return markerGeneFacets != null && !markerGeneFacets.isEmpty(); } @GetMapping(value = "/json/gene-search/organism-parts", @@ -249,7 +251,10 @@ public Set getOrganismPartBySearchTerm(@RequestParam MultiValueMap getSpeciesByGeneId(@RequestParam MultiValueMap>>> getMarkerGeneProfileByGeneIds(Optional> geneIds) { // We found expressed gene IDs, let’s get to it now... var geneIds2ExperimentAndCellIds = - geneSearchService.getCellIdsInExperiments(geneIds.get()); + geneSearchService.getCellIdsInExperiments(geneIds.orElse(null)); return geneIds2ExperimentAndCellIds.entrySet().stream() .filter(entry -> !entry.getValue().isEmpty()) diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDao.java b/app/src/main/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDao.java index 5be36c7d8..5dd14373e 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDao.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDao.java @@ -1,5 +1,6 @@ package uk.ac.ebi.atlas.search.analytics; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.apache.solr.client.solrj.SolrQuery; import org.springframework.stereotype.Component; @@ -12,6 +13,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CELL_ID; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_CELL_TYPE; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_ORGANISM; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_ORGANISM_PART; @Component public class AnalyticsSearchDao { @@ -43,6 +47,87 @@ public ImmutableSet searchFieldByCellIds( ); } + public ImmutableSet searchOrganismPartsByCellIdsAndSpecies(ImmutableSet cellIDs, + ImmutableSet species) { + var inputParams = ImmutableMap.of( + CELL_ID, cellIDs, + CTW_ORGANISM, species + ); + + return searchOutputFieldByInputFieldValues(CTW_ORGANISM_PART, inputParams); + } + + public ImmutableSet searchCellTypesByCellIdsAndSpeciesAndOrganismParts(ImmutableSet cellIDs, + ImmutableSet species, + ImmutableSet organismParts) { + var inputParams = ImmutableMap.of( + CELL_ID, cellIDs, + CTW_ORGANISM, species, + CTW_ORGANISM_PART, organismParts + ); + + return searchOutputFieldByInputFieldValues(CTW_CELL_TYPE, inputParams); + } + + private ImmutableSet searchOutputFieldByInputFieldValues( + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField outputSchemaField, + ImmutableMap> inputParams) { + var queryBuilder = getStreamBuilderForOutputField(outputSchemaField); + inputParams.forEach((key, value) -> { + if (!value.isEmpty()) { + queryBuilder.addQueryFieldByTerm(key, value); + } + }); + + var uniqueSearchStreamBuilder = new UniqueStreamBuilder( + new SearchStreamBuilder<>(singleCellAnalyticsCollectionProxy, queryBuilder).returnAllDocs(), + outputSchemaField.name()); + + return getSchemaFieldFromStreamQuery(uniqueSearchStreamBuilder, outputSchemaField.name()); + } + + private SolrQueryBuilder getStreamBuilderForOutputField( + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField outputSchemaField) { + return new SolrQueryBuilder() + .setFieldList(outputSchemaField) + .sortBy(outputSchemaField, SolrQuery.ORDER.asc); + } + + public ImmutableSet searchOutputFieldByInputFieldValues( + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField outputSchemaField, + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField inputSchemaField, + ImmutableSet inputValues) { +// Streaming query for getting the set of output field values provided by set of input field values +// unique( +// search(scxa-analytics, q=:, // could be ctw_cell_type +// fl="outputSchemaField", // could be : cell_id +// sort="outputSchemaField asc" +// ), +// over="outputSchemaField" +// ) + return getSchemaFieldFromStreamQuery( + new UniqueStreamBuilder( + getStreamBuilderByInputFieldValuesForOutputField( + inputSchemaField, inputValues, outputSchemaField), + outputSchemaField.name() + ), + outputSchemaField.name() + ); + } + + private SearchStreamBuilder getStreamBuilderByInputFieldValuesForOutputField( + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField inputSchemaField, + ImmutableSet inputValues, + SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField outputSchemaField) { + return new SearchStreamBuilder<>( + singleCellAnalyticsCollectionProxy, + new SolrQueryBuilder() + .addQueryFieldByTerm(inputSchemaField, inputValues) + .setFieldList(outputSchemaField) + .sortBy(outputSchemaField, SolrQuery.ORDER.asc) + ).returnAllDocs(); + } + private SearchStreamBuilder getStreamBuilderByCellIdsForSchemaField( ImmutableSet cellIDs, SingleCellAnalyticsCollectionProxy.SingleCellAnalyticsSchemaField schemaField) { return new SearchStreamBuilder<>( diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDao.java b/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDao.java index 3bc272b92..fe5bfff10 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDao.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDao.java @@ -12,6 +12,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CELL_ID; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_CELL_TYPE; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_ORGANISM_PART; @Component @@ -24,27 +25,33 @@ public OrganismPartSearchDao(SolrCloudCollectionProxyFactory collectionProxyFact collectionProxyFactory.create(SingleCellAnalyticsCollectionProxy.class); } - public ImmutableSet searchOrganismPart(ImmutableSet cellIDs) { + public ImmutableSet searchOrganismPart(ImmutableSet cellIDs, ImmutableSet cellTypes) { // Streaming query for getting the organism_part provided by set of cell IDs // unique( -// search(scxa-analytics-v6, q=cell_id:, +// search(scxa-analytics-v6, q=cell_id: AND cell_type:, // fl="ctw_organism_part", // sort="ctw_organism_part asc" // ), // over="ctw_organism_part" // ) return getOrganismPartFromStreamQuery( - new UniqueStreamBuilder(getStreamBuilderForOrganismPartByCellIds(cellIDs), CTW_ORGANISM_PART.name())); + new UniqueStreamBuilder(getStreamBuilderForOrganismPartByCellIds(cellIDs, cellTypes), CTW_ORGANISM_PART.name())); } private SearchStreamBuilder getStreamBuilderForOrganismPartByCellIds( - ImmutableSet cellIDs) { + ImmutableSet cellIDs, ImmutableSet cellTypes) { + var organismPartQueryBuilder = new SolrQueryBuilder() + .addQueryFieldByTerm(CELL_ID, cellIDs) + .setFieldList(CTW_ORGANISM_PART) + .sortBy(CTW_ORGANISM_PART, SolrQuery.ORDER.asc); + + if (cellTypes != null && !cellTypes.isEmpty()) { + organismPartQueryBuilder.addQueryFieldByTerm(CTW_CELL_TYPE, cellTypes); + } + return new SearchStreamBuilder<>( singleCellAnalyticsCollectionProxy, - new SolrQueryBuilder() - .addQueryFieldByTerm(CELL_ID, cellIDs) - .setFieldList(CTW_ORGANISM_PART) - .sortBy(CTW_ORGANISM_PART, SolrQuery.ORDER.asc) + organismPartQueryBuilder ).returnAllDocs(); } diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchService.java b/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchService.java index 1399da23a..a2cc5f4b0 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchService.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchService.java @@ -16,7 +16,7 @@ public class OrganismPartSearchService { private static final Logger LOGGER = LoggerFactory.getLogger(OrganismPartSearchService.class); - public ImmutableSet search(ImmutableSet geneIds) { + public ImmutableSet search(ImmutableSet geneIds, ImmutableSet cellTypes) { if (geneIds.isEmpty()) { LOGGER.warn("Can't query for organism part as no gene IDs has given."); return ImmutableSet.of(); @@ -24,7 +24,7 @@ public ImmutableSet search(ImmutableSet geneIds) { LOGGER.info("Searching organism parts for this gene ids: {}", geneIds.asList()); - return organismPartSearchDao.searchOrganismPart(geneSearchService.getCellIdsFromGeneIds(geneIds)); + return organismPartSearchDao.searchOrganismPart(geneSearchService.getCellIdsFromGeneIds(geneIds), cellTypes); } } diff --git a/app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java b/app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java new file mode 100644 index 000000000..781f1fa82 --- /dev/null +++ b/app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java @@ -0,0 +1,44 @@ +package uk.ac.ebi.atlas.solr; + +import com.google.common.collect.ImmutableSet; +import org.apache.solr.common.SolrDocumentList; +import org.springframework.stereotype.Component; +import uk.ac.ebi.atlas.solr.cloud.SolrCloudCollectionProxyFactory; +import uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy; +import uk.ac.ebi.atlas.solr.cloud.search.SolrQueryBuilder; + +import java.util.Arrays; +import java.util.Random; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CELL_ID; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_CELL_TYPE; + +@Component +public class SingleCellSolrUtils { + + private final SingleCellAnalyticsCollectionProxy singleCellAnalyticsCollectionProxy; + + private static final int MAX_ROWS = 10000; + + public SingleCellSolrUtils(SolrCloudCollectionProxyFactory solrCloudCollectionProxyFactory) { + singleCellAnalyticsCollectionProxy = + solrCloudCollectionProxyFactory.create(SingleCellAnalyticsCollectionProxy.class); + } + + public ImmutableSet fetchedRandomCellTypesByCellIDs(ImmutableSet cellIDs, int numberOfCellTypes) { + SolrQueryBuilder queryBuilder = new SolrQueryBuilder<>(); + queryBuilder + .addQueryFieldByTerm(CELL_ID, cellIDs) + .setFieldList(CTW_CELL_TYPE) + .setRows(MAX_ROWS); + + return getRandomCellTypesFromQueryResult(singleCellAnalyticsCollectionProxy.query(queryBuilder).getResults(), numberOfCellTypes); + } + + private ImmutableSet getRandomCellTypesFromQueryResult(SolrDocumentList solrDocumentList, int numberOfCellTypes) { + return Arrays.stream(new Random().ints(numberOfCellTypes, 0, solrDocumentList.size()).toArray()) + .mapToObj(index -> solrDocumentList.get(index).getFieldValue(CTW_CELL_TYPE.name()).toString()) + .collect(toImmutableSet()); + } +} diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerIT.java index 1064ddfd4..a8900d47a 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerIT.java @@ -18,6 +18,7 @@ import uk.ac.ebi.atlas.search.geneids.GeneIdSearchService; import uk.ac.ebi.atlas.search.geneids.GeneQuery; import uk.ac.ebi.atlas.search.geneids.QueryParsingException; +import uk.ac.ebi.atlas.search.organismpart.OrganismPartSearchService; import uk.ac.ebi.atlas.search.species.SpeciesSearchService; import uk.ac.ebi.atlas.trader.ExperimentTrader; @@ -47,6 +48,10 @@ class JsonGeneSearchControllerIT { @Mock private AnalyticsSearchService analyticsSearchServiceMock; + @Mock + private OrganismPartSearchService organismPartSearchServiceMock; + + @Inject private ExperimentTrader experimentTrader; @@ -67,6 +72,7 @@ void setUp() { experimentTrader, experimentAttributesService, analyticsSearchServiceMock, + organismPartSearchServiceMock, speciesSearchService); } @@ -171,7 +177,7 @@ void whenGeneIsAMarkerGeneSearchForItReturnsTrue() { } @Test - void whenRequestParamIsEmptyOrganismPartSearchReturnsEmptySet() { + void whenRequestParamIsEmptyOrganismPartSearchReturnsException() { var requestParams = new LinkedMultiValueMap(); when(geneIdSearchServiceMock.getGeneQueryByRequestParams(requestParams)) @@ -194,7 +200,7 @@ void whenSearchTermIsNotFoundAnyGeneIdsThenOrganismPartSearchReturnsEmptySet() { .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(ImmutableSet.of())); - when(analyticsSearchServiceMock.searchOrganismPart(ImmutableSet.of())) + when(organismPartSearchServiceMock.search(ImmutableSet.of(), ImmutableSet.of())) .thenReturn(ImmutableSet.of()); var emptyOrganismPartSet = subject.getOrganismPartBySearchTerm(requestParams); @@ -216,7 +222,7 @@ void whenSearchTermIsFoundButNoRelatedCellIdsThenOrganismPartSearchReturnsEmptyS .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(geneIdsFromService)); - when(analyticsSearchServiceMock.searchOrganismPart(geneIdsFromService)) + when(organismPartSearchServiceMock.search(geneIdsFromService, ImmutableSet.of())) .thenReturn(ImmutableSet.of()); var emptyOrganismPartSet = subject.getOrganismPartBySearchTerm(requestParams); @@ -239,7 +245,7 @@ void whenSearchTermIsFoundAndThereAreRelatedCellIdsThenReturnsOrganismParts() { .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(geneIdsFromService)); - when(analyticsSearchServiceMock.searchOrganismPart(geneIdsFromService)) + when(organismPartSearchServiceMock.search(geneIdsFromService, ImmutableSet.of())) .thenReturn(ImmutableSet.of(expectedOrganismPart)); var actualOrganismParts = subject.getOrganismPartBySearchTerm(requestParams); @@ -335,17 +341,6 @@ void whenRequestParamIsEmptySpeciesSearchReturnsAnException() { .isThrownBy(() -> subject.getSpeciesByGeneId(requestParams)); } - @Test - void whenRequestParamIsNullSpeciesSearchReturnsAnException() { - LinkedMultiValueMap requestParams = null; - - when(geneIdSearchServiceMock.getCategoryFromRequestParams(requestParams)) - .thenThrow(new QueryParsingException("Error parsing query")); - - assertThatExceptionOfType(QueryParsingException.class) - .isThrownBy(() -> subject.getSpeciesByGeneId(requestParams)); - } - @Test void whenGeneIdIsNotPartOfAnyExperimentThenReturnsEmptySetOfSpecies() { var requestParams = new LinkedMultiValueMap(); diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDaoIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDaoIT.java index 5800e8f49..7d6fda6dd 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDaoIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/analytics/AnalyticsSearchDaoIT.java @@ -15,14 +15,17 @@ import uk.ac.ebi.atlas.configuration.TestConfig; import uk.ac.ebi.atlas.solr.cloud.SolrCloudCollectionProxyFactory; import uk.ac.ebi.atlas.testutils.JdbcUtils; -import uk.ac.ebi.atlas.testutils.RandomDataTestUtils; import javax.inject.Inject; import javax.sql.DataSource; import static org.assertj.core.api.Assertions.assertThat; +import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CELL_ID; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_CELL_TYPE; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.CTW_ORGANISM_PART; +import static uk.ac.ebi.atlas.testutils.RandomDataTestUtils.generateRandomCellId; +import static uk.ac.ebi.atlas.testutils.RandomDataTestUtils.generateRandomOrganismPart; +import static uk.ac.ebi.atlas.testutils.RandomDataTestUtils.generateRandomSpecies; @WebAppConfiguration @ExtendWith(SpringExtension.class) @@ -45,6 +48,7 @@ public class AnalyticsSearchDaoIT { void populateDatabaseTables() { var populator = new ResourceDatabasePopulator(); populator.addScripts( + new ClassPathResource("fixtures/experiment.sql"), new ClassPathResource("fixtures/scxa_analytics.sql") ); @@ -55,7 +59,8 @@ void populateDatabaseTables() { void cleanDatabaseTables() { var populator = new ResourceDatabasePopulator(); populator.addScripts( - new ClassPathResource("fixtures/scxa_analytics-delete.sql") + new ClassPathResource("fixtures/scxa_analytics-delete.sql"), + new ClassPathResource("fixtures/experiment-delete.sql") ); populator.execute(dataSource); } @@ -69,7 +74,8 @@ void setup() { void whenEmptySetOfCellIDsProvidedReturnEmptySetOfOrganismPart() { var cellIDs = ImmutableSet.of(); - var organismParts = subject.searchFieldByCellIds(CTW_ORGANISM_PART, cellIDs); + var organismParts = subject.searchOutputFieldByInputFieldValues( + CTW_ORGANISM_PART, CELL_ID, cellIDs); assertThat(organismParts).isEmpty(); } @@ -78,12 +84,13 @@ void whenEmptySetOfCellIDsProvidedReturnEmptySetOfOrganismPart() { void whenInvalidCellIdsProvidedReturnEmptySetOfOrganismPart() { var cellIDs = ImmutableSet.of( - RandomDataTestUtils.generateRandomCellId(), - RandomDataTestUtils.generateRandomCellId(), - RandomDataTestUtils.generateRandomCellId() + generateRandomCellId(), + generateRandomCellId(), + generateRandomCellId() ); - var organismParts = subject.searchFieldByCellIds(CTW_ORGANISM_PART, cellIDs); + var organismParts = subject.searchOutputFieldByInputFieldValues( + CTW_ORGANISM_PART, CELL_ID, cellIDs); assertThat(organismParts).isEmpty(); } @@ -93,7 +100,8 @@ void whenValidCellIdsProvidedReturnSetOfOrganismPart() { var cellIDs = ImmutableSet.copyOf(jdbcUtils.fetchRandomListOfCells(10)); - var organismParts = subject.searchFieldByCellIds(CTW_ORGANISM_PART, cellIDs); + var organismParts = subject.searchOutputFieldByInputFieldValues( + CTW_ORGANISM_PART, CELL_ID, cellIDs); assertThat(organismParts.size()).isGreaterThan(0); } @@ -102,7 +110,8 @@ void whenValidCellIdsProvidedReturnSetOfOrganismPart() { void whenEmptySetOfCellIdsProvidedReturnEmptySetOfCellType() { var cellIDs = ImmutableSet.of(); - var cellTypes = subject.searchFieldByCellIds(CTW_CELL_TYPE, cellIDs); + var cellTypes = subject.searchOutputFieldByInputFieldValues( + CTW_CELL_TYPE, CELL_ID, cellIDs); assertThat(cellTypes).isEmpty(); } @@ -111,12 +120,13 @@ void whenEmptySetOfCellIdsProvidedReturnEmptySetOfCellType() { void whenInvalidCellIdsProvidedReturnEmptySetOfCellType() { var cellIDs = ImmutableSet.of( - RandomDataTestUtils.generateRandomCellId(), - RandomDataTestUtils.generateRandomCellId(), - RandomDataTestUtils.generateRandomCellId() + generateRandomCellId(), + generateRandomCellId(), + generateRandomCellId() ); - var cellTypes = subject.searchFieldByCellIds(CTW_CELL_TYPE, cellIDs); + var cellTypes = subject.searchOutputFieldByInputFieldValues( + CTW_CELL_TYPE, CELL_ID, cellIDs); assertThat(cellTypes).isEmpty(); } @@ -126,8 +136,80 @@ void whenValidCellIdsProvidedReturnSetOfCellTypes() { var cellIDs = ImmutableSet.copyOf(jdbcUtils.fetchRandomListOfCells(10)); - var cellTypes = subject.searchFieldByCellIds(CTW_CELL_TYPE, cellIDs); + var cellTypes = subject.searchOutputFieldByInputFieldValues( + CTW_CELL_TYPE, CELL_ID, cellIDs); assertThat(cellTypes.size()).isGreaterThan(0); } + + @Test + void whenInvalidParametersProvidedReturnEmptySetOfOrganismParts() { + var cellIDs = ImmutableSet.of( + generateRandomCellId(), + generateRandomCellId(), + generateRandomCellId() + ); + var species = ImmutableSet.of( + generateRandomSpecies().getName(), + generateRandomSpecies().getName(), + generateRandomSpecies().getName() + ); + + var actualOrganismParts = subject.searchOrganismPartsByCellIdsAndSpecies( + cellIDs, species); + + assertThat(actualOrganismParts).isEmpty(); + } + + @Test + void whenValidCellIDsAndSpeciesProvidedReturnSetOfOrganismParts() { + var experimentAccession = "E-EHCA-2"; + var cellIDs = ImmutableSet.copyOf( + jdbcUtils.fetchRandomListOfCellsFromExperiment(experimentAccession, 10)); + var species = ImmutableSet.of( + jdbcUtils.fetchSpeciesByExperimentAccession(experimentAccession)); + + var actualOrganismParts = subject.searchOrganismPartsByCellIdsAndSpecies(cellIDs, species); + + assertThat(actualOrganismParts.size()).isGreaterThan(0); + } + + @Test + void whenInvalidParametersProvidedReturnEmptySetOfCellTypes() { + var cellIDs = ImmutableSet.of( + generateRandomCellId(), + generateRandomCellId(), + generateRandomCellId() + ); + var species = ImmutableSet.of( + generateRandomSpecies().getName(), + generateRandomSpecies().getName(), + generateRandomSpecies().getName() + ); + var organismParts = ImmutableSet.of( + generateRandomOrganismPart(), + generateRandomOrganismPart() + ); + + var actualCellTypes = subject.searchCellTypesByCellIdsAndSpeciesAndOrganismParts( + cellIDs, species, organismParts); + + assertThat(actualCellTypes).isEmpty(); + } + + @Test // TODO replace hardcoded values with random ones if it is possible + void whenValidParametersProvidedReturnSetOfCellTypes() { + var experimentAccession = "E-CURD-4"; + var species = ImmutableSet.of( + jdbcUtils.fetchSpeciesByExperimentAccession(experimentAccession)); + // hardcoded values for cellIds and organism parts as we don't have fixtures for Solr + var cellIDs = ImmutableSet.of( + "SRR8206654-ATTGTTAGTAGT", "SRR8206662-ATCTCGCTCCCC", "SRR8206662-CAGGATTAAGCC"); + var organismParts = ImmutableSet.of("root"); + + var actualOrganismParts = subject.searchCellTypesByCellIdsAndSpeciesAndOrganismParts( + cellIDs, species, organismParts); + + assertThat(actualOrganismParts.size()).isGreaterThan(0); + } } diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDaoIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDaoIT.java index edf87c1dd..5f782651a 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDaoIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDaoIT.java @@ -15,11 +15,11 @@ import uk.ac.ebi.atlas.configuration.TestConfig; import uk.ac.ebi.atlas.solr.cloud.SolrCloudCollectionProxyFactory; import uk.ac.ebi.atlas.testutils.JdbcUtils; +import uk.ac.ebi.atlas.solr.SingleCellSolrUtils; import javax.inject.Inject; import javax.sql.DataSource; import java.util.HashSet; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +32,9 @@ public class OrganismPartSearchDaoIT { @Inject private JdbcUtils jdbcUtils; + @Inject + private SingleCellSolrUtils solrUtils; + @Inject private DataSource dataSource; @@ -65,32 +68,58 @@ void setup() { } @Test - void whenEmptySetOfCellIDsProvidedReturnEmptySetOfOrganismPart() { - ImmutableSet cellIDs = ImmutableSet.of(); + void whenEmptySetOfCellIDsAndCellTypesProvidedReturnEmptySetOfOrganismPart() { + ImmutableSet emptyCellIDs = ImmutableSet.of(); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(emptyCellIDs, emptySetOfCellTypes); assertThat(organismParts).isEmpty(); } @Test - void whenInvalidCellIdsProvidedReturnEmptySetOfOrganismPart() { - var cellIDs = + void whenInvalidCellIdsAndNoCellTypesProvidedReturnEmptySetOfOrganismPart() { + var invalidCellIDs = ImmutableSet.of("invalid-cellID-1", "invalid-cellID-2", "invalid-cellID-3"); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); + + var organismParts = subject.searchOrganismPart(invalidCellIDs, emptySetOfCellTypes); + + assertThat(organismParts).isEmpty(); + } + + @Test + void whenOnlyValidCellIdsButNoCellTypesProvidedReturnSetOfOrganismPart() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(10))); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); + + var organismParts = subject.searchOrganismPart(randomListOfCellIDs, emptySetOfCellTypes); + + assertThat(organismParts.size()).isGreaterThan(0); + } + + @Test + void whenValidCellIdsButInvalidCellTypesProvidedReturnEmptySetOfOrganismPart() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(10))); + ImmutableSet invalidCellTypes = ImmutableSet.of("invalid-cellType-1", "invalid-cellType-2"); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(randomListOfCellIDs, invalidCellTypes); assertThat(organismParts).isEmpty(); } @Test - void whenValidCellIdsProvidedReturnSetOfOrganismPart() { - final List randomListOfCellIDs = jdbcUtils.fetchRandomListOfCells(10); - var cellIDs = + void whenValidCellIdsAndValidCellTypesProvidedReturnSetOfOrganismPart() { + var randomListOfCellIDs = ImmutableSet.copyOf( - new HashSet<>(randomListOfCellIDs)); + new HashSet<>(jdbcUtils.fetchRandomListOfCells(3))); + ImmutableSet cellTypes = solrUtils.fetchedRandomCellTypesByCellIDs(randomListOfCellIDs, 1); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(randomListOfCellIDs, cellTypes); assertThat(organismParts.size()).isGreaterThan(0); } diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java index 5a86443f4..8d7165877 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java @@ -31,48 +31,75 @@ void setup() { } @Test - void whenEmptySetOfGeneIdsProvidedReturnEmptySetOfOrganismPart() { + void whenEmptySetOfGeneIdsAndCellTypesProvidedReturnsEmptySetOfOrganismPart() { ImmutableSet emptySetOfGeneIds = ImmutableSet.of(); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - var emptySetOfOrganismParts = subject.search(emptySetOfGeneIds); + var organismParts = subject.search(emptySetOfGeneIds, emptySetOfCellTypes); - assertThat(emptySetOfOrganismParts).isEmpty(); + assertThat(organismParts).isEmpty(); } @Test - void whenNonExistentGeneIdsGivenReturnEmptySetOfOrganismPart() { + void whenNonExistentGeneIdsAndEmptySetOfCellTypesProvidedReturnsEmptySetOfOrganismPart() { var nonExistentGeneId = "nonExistentGeneId"; - var emptySetOfGeneIds = ImmutableSet.of(nonExistentGeneId); + var setOfNonExistentGeneIds = ImmutableSet.of(nonExistentGeneId); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - when(geneSearchService.getCellIdsFromGeneIds(emptySetOfGeneIds)) + when(geneSearchService.getCellIdsFromGeneIds(setOfNonExistentGeneIds)) .thenReturn(ImmutableSet.of()); - when(organismPartSearchDao.searchOrganismPart(ImmutableSet.of())) + when(organismPartSearchDao.searchOrganismPart(ImmutableSet.of(), emptySetOfCellTypes)) .thenReturn(ImmutableSet.of()); - var emptySetOfOrganismParts = subject.search(emptySetOfGeneIds); + var organismParts = subject.search(setOfNonExistentGeneIds, emptySetOfCellTypes); - assertThat(emptySetOfOrganismParts).isEmpty(); + assertThat(organismParts).isEmpty(); + } + + @Test + void whenValidGeneIdsAndEmptySetOfCellTypesProvidedReturnsSetOfOrganismParts() { + var existingGeneId1 = "ExistingGeneId1"; + var existingGeneId2 = "ExistingGeneId2"; + var geneIds = ImmutableSet.of(existingGeneId1, existingGeneId2); + var existingCellId1 = "ExistingCellId1"; + var existingCellId2 = "ExistingCellId2"; + var cellIds = ImmutableSet.of(existingCellId1, existingCellId2); + + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); + + var expectedOrganismPart = "primary visual cortex"; + + when(geneSearchService.getCellIdsFromGeneIds(geneIds)) + .thenReturn(cellIds); + when(organismPartSearchDao.searchOrganismPart(cellIds, emptySetOfCellTypes)) + .thenReturn(ImmutableSet.of(expectedOrganismPart)); + + var organismParts = subject.search(geneIds, emptySetOfCellTypes); + assertThat(organismParts).contains(expectedOrganismPart); } @Test - void whenValidGeneIdsGivenReturnSetOfOrganismParts() { + void whenValidGeneIDsAndCellTypesProvidedReturnsSetOfOrganismParts() { var existingGeneId1 = "ExistingGeneId1"; var existingGeneId2 = "ExistingGeneId2"; - var validGeneIds = ImmutableSet.of(existingGeneId1, existingGeneId2); + var geneIds = ImmutableSet.of(existingGeneId1, existingGeneId2); var existingCellId1 = "ExistingCellId1"; var existingCellId2 = "ExistingCellId2"; - var validCellIds = ImmutableSet.of(existingCellId1, existingCellId2); + var cellIds = ImmutableSet.of(existingCellId1, existingCellId2); + var existingCellType1 = "ExistingCellType1"; + var existingCellType2 = "ExistingCellType2"; + var cellTypes = ImmutableSet.of(existingCellType1, existingCellType2); var expectedOrganismPart = "primary visual cortex"; - when(geneSearchService.getCellIdsFromGeneIds(validGeneIds)) - .thenReturn(validCellIds); - when(organismPartSearchDao.searchOrganismPart(validCellIds)) + when(geneSearchService.getCellIdsFromGeneIds(geneIds)) + .thenReturn(cellIds); + when(organismPartSearchDao.searchOrganismPart(cellIds, cellTypes)) .thenReturn(ImmutableSet.of(expectedOrganismPart)); - var actualSetOfOrganismParts = subject.search(validGeneIds); + var organismParts = subject.search(geneIds, cellTypes); - assertThat(actualSetOfOrganismParts).contains(expectedOrganismPart); + assertThat(organismParts).contains(expectedOrganismPart); } } diff --git a/atlas-web-core b/atlas-web-core index f67a743b4..2dcf1d8e0 160000 --- a/atlas-web-core +++ b/atlas-web-core @@ -1 +1 @@ -Subproject commit f67a743b48c12602a0da251d93c676a7f31adb64 +Subproject commit 2dcf1d8e0e19f58be6971a3f642a50e2da3c1023