From ccfe8ff27b76a01ad8481746c6953883fa1fa0f8 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 1 Feb 2023 14:51:21 +0000 Subject: [PATCH 01/13] Pull and update latest submodule changes --- atlas-web-core | 2 +- schemas | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atlas-web-core b/atlas-web-core index 927e0df85..3e2b2fa1d 160000 --- a/atlas-web-core +++ b/atlas-web-core @@ -1 +1 @@ -Subproject commit 927e0df857274f44e7e595e22c7bba374f504c22 +Subproject commit 3e2b2fa1d3fe6008b3651a731e952906f1dd9a39 diff --git a/schemas b/schemas index 33a9b2e1d..80545112b 160000 --- a/schemas +++ b/schemas @@ -1 +1 @@ -Subproject commit 33a9b2e1d0ff05c22da81aa35cfb0dda7fe06b2c +Subproject commit 80545112bf373ccacfe46a53787aa07a4ef0b971 From 9b30847ad1dad894136fea6b7af74d589669fcf8 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 14 Jun 2023 15:44:08 +0100 Subject: [PATCH 02/13] Add organism part and cell type search to analytics search DAO --- .../search/analytics/AnalyticsSearchDao.java | 85 ++++++++++++++ .../analytics/AnalyticsSearchDaoIT.java | 110 +++++++++++++++--- atlas-web-core | 2 +- 3 files changed, 182 insertions(+), 15 deletions(-) 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/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/atlas-web-core b/atlas-web-core index c2155e6a6..f0bd77685 160000 --- a/atlas-web-core +++ b/atlas-web-core @@ -1 +1 @@ -Subproject commit c2155e6a6784d7bbe27ad947a96cbc2b10caa743 +Subproject commit f0bd77685c933e2aa6e70b212335f7314489ccbc From c90387d8cdb2a217a381191fb1a2ed8a449566bc Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Fri, 28 Jul 2023 14:05:53 +0100 Subject: [PATCH 03/13] Clean variable naming --- .../organismpart/OrganismPartSearchDaoIT.java | 16 +++++++--------- .../OrganismPartSearchServiceTest.java | 16 ++++++++-------- 2 files changed, 15 insertions(+), 17 deletions(-) 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..78f6bee07 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 @@ -19,7 +19,6 @@ import javax.inject.Inject; import javax.sql.DataSource; import java.util.HashSet; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -66,31 +65,30 @@ void setup() { @Test void whenEmptySetOfCellIDsProvidedReturnEmptySetOfOrganismPart() { - ImmutableSet cellIDs = ImmutableSet.of(); + ImmutableSet emptyCellIDs = ImmutableSet.of(); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(emptyCellIDs); assertThat(organismParts).isEmpty(); } @Test void whenInvalidCellIdsProvidedReturnEmptySetOfOrganismPart() { - var cellIDs = + var invalidCellIDs = ImmutableSet.of("invalid-cellID-1", "invalid-cellID-2", "invalid-cellID-3"); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(invalidCellIDs); assertThat(organismParts).isEmpty(); } @Test void whenValidCellIdsProvidedReturnSetOfOrganismPart() { - final List randomListOfCellIDs = jdbcUtils.fetchRandomListOfCells(10); - var cellIDs = + var randomListOfCellIDs = ImmutableSet.copyOf( - new HashSet<>(randomListOfCellIDs)); + new HashSet<>(jdbcUtils.fetchRandomListOfCells(10))); - var organismParts = subject.searchOrganismPart(cellIDs); + var organismParts = subject.searchOrganismPart(randomListOfCellIDs); 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..eda322631 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 @@ -34,24 +34,24 @@ void setup() { void whenEmptySetOfGeneIdsProvidedReturnEmptySetOfOrganismPart() { ImmutableSet emptySetOfGeneIds = ImmutableSet.of(); - var emptySetOfOrganismParts = subject.search(emptySetOfGeneIds); + var organismParts = subject.search(emptySetOfGeneIds); - assertThat(emptySetOfOrganismParts).isEmpty(); + assertThat(organismParts).isEmpty(); } @Test void whenNonExistentGeneIdsGivenReturnEmptySetOfOrganismPart() { var nonExistentGeneId = "nonExistentGeneId"; - var emptySetOfGeneIds = ImmutableSet.of(nonExistentGeneId); + var setOfNonExistentGeneIds = ImmutableSet.of(nonExistentGeneId); - when(geneSearchService.getCellIdsFromGeneIds(emptySetOfGeneIds)) + when(geneSearchService.getCellIdsFromGeneIds(setOfNonExistentGeneIds)) .thenReturn(ImmutableSet.of()); when(organismPartSearchDao.searchOrganismPart(ImmutableSet.of())) .thenReturn(ImmutableSet.of()); - var emptySetOfOrganismParts = subject.search(emptySetOfGeneIds); + var organismParts = subject.search(setOfNonExistentGeneIds); - assertThat(emptySetOfOrganismParts).isEmpty(); + assertThat(organismParts).isEmpty(); } @@ -71,8 +71,8 @@ void whenValidGeneIdsGivenReturnSetOfOrganismParts() { when(organismPartSearchDao.searchOrganismPart(validCellIds)) .thenReturn(ImmutableSet.of(expectedOrganismPart)); - var actualSetOfOrganismParts = subject.search(validGeneIds); + var organismParts = subject.search(validGeneIds); - assertThat(actualSetOfOrganismParts).contains(expectedOrganismPart); + assertThat(organismParts).contains(expectedOrganismPart); } } From 5f79f72bf96d6389b78987a0d1f5a4a2e2d7a6c8 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Tue, 1 Aug 2023 13:49:57 +0100 Subject: [PATCH 04/13] Add cell types as an input parameter to OrganismPartSearchDao --- .../organismpart/OrganismPartSearchDao.java | 23 ++++++---- .../ebi/atlas/solr/SingleCellSolrUtils.java | 44 +++++++++++++++++++ .../organismpart/OrganismPartSearchDaoIT.java | 43 +++++++++++++++--- 3 files changed, 96 insertions(+), 14 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/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/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/organismpart/OrganismPartSearchDaoIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchDaoIT.java index 78f6bee07..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,6 +15,7 @@ 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; @@ -31,6 +32,9 @@ public class OrganismPartSearchDaoIT { @Inject private JdbcUtils jdbcUtils; + @Inject + private SingleCellSolrUtils solrUtils; + @Inject private DataSource dataSource; @@ -64,31 +68,58 @@ void setup() { } @Test - void whenEmptySetOfCellIDsProvidedReturnEmptySetOfOrganismPart() { + void whenEmptySetOfCellIDsAndCellTypesProvidedReturnEmptySetOfOrganismPart() { ImmutableSet emptyCellIDs = ImmutableSet.of(); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - var organismParts = subject.searchOrganismPart(emptyCellIDs); + var organismParts = subject.searchOrganismPart(emptyCellIDs, emptySetOfCellTypes); assertThat(organismParts).isEmpty(); } @Test - void whenInvalidCellIdsProvidedReturnEmptySetOfOrganismPart() { + void whenInvalidCellIdsAndNoCellTypesProvidedReturnEmptySetOfOrganismPart() { var invalidCellIDs = ImmutableSet.of("invalid-cellID-1", "invalid-cellID-2", "invalid-cellID-3"); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - var organismParts = subject.searchOrganismPart(invalidCellIDs); + var organismParts = subject.searchOrganismPart(invalidCellIDs, emptySetOfCellTypes); assertThat(organismParts).isEmpty(); } @Test - void whenValidCellIdsProvidedReturnSetOfOrganismPart() { + 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(randomListOfCellIDs, invalidCellTypes); + + assertThat(organismParts).isEmpty(); + } + + @Test + void whenValidCellIdsAndValidCellTypesProvidedReturnSetOfOrganismPart() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(3))); + ImmutableSet cellTypes = solrUtils.fetchedRandomCellTypesByCellIDs(randomListOfCellIDs, 1); - var organismParts = subject.searchOrganismPart(randomListOfCellIDs); + var organismParts = subject.searchOrganismPart(randomListOfCellIDs, cellTypes); assertThat(organismParts.size()).isGreaterThan(0); } From 2a610584ccc8c60f80bbd7abcc7a886601758389 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Tue, 1 Aug 2023 14:46:24 +0100 Subject: [PATCH 05/13] Add cell types as an input parameter to OrganismPartSearchService --- .../OrganismPartSearchService.java | 4 +- .../OrganismPartSearchServiceTest.java | 51 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) 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/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java b/app/src/test/java/uk/ac/ebi/atlas/search/organismpart/OrganismPartSearchServiceTest.java index eda322631..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,47 +31,74 @@ void setup() { } @Test - void whenEmptySetOfGeneIdsProvidedReturnEmptySetOfOrganismPart() { + void whenEmptySetOfGeneIdsAndCellTypesProvidedReturnsEmptySetOfOrganismPart() { ImmutableSet emptySetOfGeneIds = ImmutableSet.of(); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); - var organismParts = subject.search(emptySetOfGeneIds); + var organismParts = subject.search(emptySetOfGeneIds, emptySetOfCellTypes); assertThat(organismParts).isEmpty(); } @Test - void whenNonExistentGeneIdsGivenReturnEmptySetOfOrganismPart() { + void whenNonExistentGeneIdsAndEmptySetOfCellTypesProvidedReturnsEmptySetOfOrganismPart() { var nonExistentGeneId = "nonExistentGeneId"; var setOfNonExistentGeneIds = ImmutableSet.of(nonExistentGeneId); + ImmutableSet emptySetOfCellTypes = ImmutableSet.of(); when(geneSearchService.getCellIdsFromGeneIds(setOfNonExistentGeneIds)) .thenReturn(ImmutableSet.of()); - when(organismPartSearchDao.searchOrganismPart(ImmutableSet.of())) + when(organismPartSearchDao.searchOrganismPart(ImmutableSet.of(), emptySetOfCellTypes)) .thenReturn(ImmutableSet.of()); - var organismParts = subject.search(setOfNonExistentGeneIds); + var organismParts = subject.search(setOfNonExistentGeneIds, emptySetOfCellTypes); 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 organismParts = subject.search(validGeneIds); + var organismParts = subject.search(geneIds, cellTypes); assertThat(organismParts).contains(expectedOrganismPart); } From 359c11706c28d35fead92e5e417c8305b05fab4f Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Tue, 1 Aug 2023 14:55:09 +0100 Subject: [PATCH 06/13] clean code in JsonGeneSearchController --- .../uk/ac/ebi/atlas/search/JsonGeneSearchController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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..23d24a520 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 @@ -27,6 +27,7 @@ import java.util.AbstractMap.SimpleEntry; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import static com.google.common.base.Strings.isNullOrEmpty; @@ -130,7 +131,7 @@ 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())) ? + (Objects.requireNonNull(geneIds.orElse(null)).size() == 1 && !geneIds.get().iterator().next().equals(geneQuery.queryTerm())) ? "(" + String.join(", ", geneIds.get()) + ")" : ""; @@ -182,7 +183,6 @@ public String searchForGene(@RequestParam MultiValueMap requestP // 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( @@ -236,7 +236,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", From 1913dcf19006931ace6eaf570d4963c05894671b Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Tue, 1 Aug 2023 17:16:58 +0100 Subject: [PATCH 07/13] remove unneeded JdbcUtils from CellTypeSearchDaoIT --- .../uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java index 02c606d19..075770588 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java @@ -14,7 +14,6 @@ import org.springframework.test.context.web.WebAppConfiguration; import uk.ac.ebi.atlas.configuration.TestConfig; import uk.ac.ebi.atlas.solr.cloud.SolrCloudCollectionProxyFactory; -import uk.ac.ebi.atlas.testutils.JdbcUtils; import javax.inject.Inject; import javax.sql.DataSource; @@ -27,9 +26,6 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class CellTypeSearchDaoIT { - @Inject - private JdbcUtils jdbcUtils; - @Inject private DataSource dataSource; From 06bdccf1fb358ac25e5b2f6fe4bbbf7b394672c6 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 2 Aug 2023 17:17:53 +0100 Subject: [PATCH 08/13] Add cell type query parameter to the JsonGeneSearchController - WIP --- .../ebi/atlas/search/JsonGeneSearchController.java | 7 ++++++- .../ebi/atlas/search/JsonGeneSearchControllerIT.java | 12 +++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) 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 23d24a520..fe4c3ddb4 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; @@ -48,6 +49,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) @@ -249,7 +251,10 @@ public Set getOrganismPartBySearchTerm(@RequestParam MultiValueMap Date: Thu, 3 Aug 2023 11:58:02 +0100 Subject: [PATCH 09/13] Rename test method name and remove unneeded test case --- .../atlas/search/JsonGeneSearchControllerIT.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 bc8ace667..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 @@ -177,7 +177,7 @@ void whenGeneIsAMarkerGeneSearchForItReturnsTrue() { } @Test - void whenRequestParamIsEmptyOrganismPartSearchReturnsEmptySet() { + void whenRequestParamIsEmptyOrganismPartSearchReturnsException() { var requestParams = new LinkedMultiValueMap(); when(geneIdSearchServiceMock.getGeneQueryByRequestParams(requestParams)) @@ -341,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(); From 949e76098e52795c7d11c1f7397fb0e2fab1a105 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Fri, 4 Aug 2023 10:11:32 +0100 Subject: [PATCH 10/13] Add organism parts parameter to CellTypeSearchDao --- .../search/celltype/CellTypeSearchDao.java | 52 ++++++++++-- .../organismpart/OrganismPartSearchDao.java | 6 +- .../ebi/atlas/solr/SingleCellSolrUtils.java | 27 ++++++- .../search/celltype/CellTypeSearchDaoIT.java | 81 ++++++++++++++++++- 4 files changed, 151 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java index e18537aee..03fcd4830 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java @@ -19,6 +19,8 @@ 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; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.EXPERIMENT_ACCESSION; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.FACET_CHARACTERISTIC_NAME; import static uk.ac.ebi.atlas.solr.cloud.collections.SingleCellAnalyticsCollectionProxy.FACET_CHARACTERISTIC_VALUE; @@ -37,9 +39,9 @@ public class CellTypeSearchDao { private final SingleCellAnalyticsCollectionProxy singleCellAnalyticsCollectionProxy; - public CellTypeSearchDao(SolrCloudCollectionProxyFactory solrCloudCollectionProxyFactory) { + public CellTypeSearchDao(SolrCloudCollectionProxyFactory collectionProxyFactory) { this.singleCellAnalyticsCollectionProxy = - solrCloudCollectionProxyFactory.create(SingleCellAnalyticsCollectionProxy.class); + collectionProxyFactory.create(SingleCellAnalyticsCollectionProxy.class); } @Cacheable(cacheNames = "inferredCellTypesOntology", key = "{#experimentAccession, #organOrOrganismPart}") @@ -100,7 +102,7 @@ public ImmutableSet getInferredCellTypeAuthorsLabels(String experimentAc */ private ImmutableSet getCellTypeMetadata(String experimentAccession, ImmutableSet organOrOrganismPart, - String cellTypeValue) { + String cellTypeFacetName) { var cellIdsInOrganOrOrganismPartQueryBuilder = new SolrQueryBuilder() .addQueryFieldByTerm(EXPERIMENT_ACCESSION, experimentAccession) @@ -124,8 +126,8 @@ private ImmutableSet getCellTypeMetadata(String experimentAccession, .addQueryFieldByTerm(EXPERIMENT_ACCESSION, experimentAccession) .addQueryFieldByTerm( ImmutableMap.of( - FACET_FACTOR_NAME, ImmutableSet.of(cellTypeValue), - FACET_CHARACTERISTIC_NAME, ImmutableSet.of(cellTypeValue))) + FACET_FACTOR_NAME, ImmutableSet.of(cellTypeFacetName), + FACET_CHARACTERISTIC_NAME, ImmutableSet.of(cellTypeFacetName))) .setFieldList(ImmutableSet.of(CELL_ID, FACET_FACTOR_VALUE, FACET_CHARACTERISTIC_VALUE)) .sortBy(CELL_ID, SolrQuery.ORDER.asc); var uniqueCellIdsAnnotatedWithCellTypeValue = @@ -162,4 +164,44 @@ private ImmutableSet getCellTypeMetadata(String experimentAccession, .collect(toImmutableSet()); } } + + public ImmutableSet searchCellTypes(ImmutableSet cellIds, ImmutableSet organismParts) { +// Streaming query for getting the cell types provided by set of cell IDs and organism parts +// unique( +// search(scxa-analytics-v6, q=cell_id: AND ctw_organism_part:, +// fl="ctw_cell_type", +// sort="ctw_cell_type asc" +// ), +// over="ctw_cell_type" +// ) + return getCellTypeFromStreamQuery( + new UniqueStreamBuilder(getStreamBuilderForCellTypeByCellIdsAndOrganismParts( + cellIds, organismParts), CTW_CELL_TYPE.name())); + } + + private SearchStreamBuilder getStreamBuilderForCellTypeByCellIdsAndOrganismParts( + ImmutableSet cellIDs, ImmutableSet organismParts) { + var cellTypeQueryBuilder = new SolrQueryBuilder() + .addQueryFieldByTerm(CELL_ID, cellIDs) + .setFieldList(CTW_CELL_TYPE) + .sortBy(CTW_CELL_TYPE, SolrQuery.ORDER.asc); + + if (organismParts != null && !organismParts.isEmpty()) { + cellTypeQueryBuilder.addQueryFieldByTerm(CTW_ORGANISM_PART, organismParts); + } + + return new SearchStreamBuilder<>( + singleCellAnalyticsCollectionProxy, + cellTypeQueryBuilder + ).returnAllDocs(); + } + + private ImmutableSet getCellTypeFromStreamQuery(UniqueStreamBuilder uniqueCellTypeStreamBuilder) { + try (TupleStreamer tupleStreamer = TupleStreamer.of(uniqueCellTypeStreamBuilder.build())) { + return tupleStreamer.get() + .map(tuple -> tuple.getString(CTW_CELL_TYPE.name())) + .collect(toImmutableSet() + ); + } + } } \ No newline at end of file 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 fe5bfff10..81b31556a 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 @@ -25,10 +25,10 @@ public OrganismPartSearchDao(SolrCloudCollectionProxyFactory collectionProxyFact collectionProxyFactory.create(SingleCellAnalyticsCollectionProxy.class); } - public ImmutableSet searchOrganismPart(ImmutableSet cellIDs, ImmutableSet cellTypes) { -// Streaming query for getting the organism_part provided by set of cell IDs + public ImmutableSet searchOrganismPart(ImmutableSet cellIDs, ImmutableSet cellTypes) { +// Streaming query for getting the organism_part provided by set of cell IDs and cell types // unique( -// search(scxa-analytics-v6, q=cell_id: AND cell_type:, +// search(scxa-analytics-v6, q=cell_id: AND ctw_cell_type:, // fl="ctw_organism_part", // sort="ctw_organism_part asc" // ), 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 index 781f1fa82..8d16dcf90 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java +++ b/app/src/main/java/uk/ac/ebi/atlas/solr/SingleCellSolrUtils.java @@ -13,6 +13,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 public class SingleCellSolrUtils { @@ -33,12 +34,32 @@ public ImmutableSet fetchedRandomCellTypesByCellIDs(ImmutableSet .setFieldList(CTW_CELL_TYPE) .setRows(MAX_ROWS); - return getRandomCellTypesFromQueryResult(singleCellAnalyticsCollectionProxy.query(queryBuilder).getResults(), numberOfCellTypes); + return getRandomCellTypesFromQueryResult( + singleCellAnalyticsCollectionProxy.query(queryBuilder).getResults(), + CTW_CELL_TYPE.name(), + numberOfCellTypes); } - private ImmutableSet getRandomCellTypesFromQueryResult(SolrDocumentList solrDocumentList, int numberOfCellTypes) { + public ImmutableSet fetchedRandomOrganismPartsByCellIDs(ImmutableSet cellIDs, int numberOfOrganismParts) { + SolrQueryBuilder queryBuilder = new SolrQueryBuilder<>(); + queryBuilder + .addQueryFieldByTerm(CELL_ID, cellIDs) + .setFieldList(CTW_ORGANISM_PART) + .setRows(MAX_ROWS); + + return getRandomCellTypesFromQueryResult( + singleCellAnalyticsCollectionProxy.query(queryBuilder).getResults(), + CTW_ORGANISM_PART.name(), + numberOfOrganismParts); + } + + + private ImmutableSet getRandomCellTypesFromQueryResult( + SolrDocumentList solrDocumentList, + String schemaFieldName, + int numberOfCellTypes) { return Arrays.stream(new Random().ints(numberOfCellTypes, 0, solrDocumentList.size()).toArray()) - .mapToObj(index -> solrDocumentList.get(index).getFieldValue(CTW_CELL_TYPE.name()).toString()) + .mapToObj(index -> solrDocumentList.get(index).getFieldValue(schemaFieldName).toString()) .collect(toImmutableSet()); } } diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java index 075770588..700f7a1ae 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDaoIT.java @@ -13,11 +13,15 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; import uk.ac.ebi.atlas.configuration.TestConfig; +import uk.ac.ebi.atlas.solr.SingleCellSolrUtils; import uk.ac.ebi.atlas.solr.cloud.SolrCloudCollectionProxyFactory; +import uk.ac.ebi.atlas.testutils.JdbcUtils; import javax.inject.Inject; import javax.sql.DataSource; +import java.util.HashSet; + import static org.assertj.core.api.Assertions.assertThat; @WebAppConfiguration @@ -26,6 +30,12 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class CellTypeSearchDaoIT { + @Inject + private JdbcUtils jdbcUtils; + + @Inject + private SingleCellSolrUtils solrUtils; + @Inject private DataSource dataSource; @@ -38,6 +48,7 @@ class CellTypeSearchDaoIT { void populateDatabaseTables() { var populator = new ResourceDatabasePopulator(); populator.addScripts( + new ClassPathResource("fixtures/experiment.sql"), new ClassPathResource("fixtures/scxa_analytics.sql") ); @@ -48,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); } @@ -60,9 +72,10 @@ void setUp() { @Test void nonExistentValueReturnsEmptyCollection() { - assertThat(subject.getInferredCellTypeOntologyLabels("E-MTAB-5061", ImmutableSet.of("foobar"))) + var experimentAccessions = jdbcUtils.fetchRandomExperimentAccession(); + assertThat(subject.getInferredCellTypeOntologyLabels(experimentAccessions, ImmutableSet.of("foobar"))) .isEmpty(); - assertThat(subject.getInferredCellTypeAuthorsLabels("E-MTAB-5061", ImmutableSet.of("foobar"))) + assertThat(subject.getInferredCellTypeAuthorsLabels(experimentAccessions, ImmutableSet.of("foobar"))) .isEmpty(); } @@ -114,7 +127,67 @@ void ontologyLabelsAcceptMultipleOrganismParts(){ void authorsLabelsAcceptMultipleOrganismParts(){ var authorsLabels = subject.getInferredCellTypeAuthorsLabels( - "E-MTAB-5061", ImmutableSet.of("http://purl.obolibrary.org/obo/UBERON_0000006","http://purl.obolibrary.org/obo/UBERON_0001264")); + "E-MTAB-5061", + ImmutableSet.of("http://purl.obolibrary.org/obo/UBERON_0000006", + "http://purl.obolibrary.org/obo/UBERON_0001264")); assertThat(authorsLabels).isNotEmpty(); } + + @Test + void whenEmptySetOfCellIDsAndOrganismPartsProvidedReturnEmptySetOfCellTypes() { + ImmutableSet emptyCellIDs = ImmutableSet.of(); + ImmutableSet emptySetOfOrganismParts = ImmutableSet.of(); + + var cellTypes = subject.searchCellTypes(emptyCellIDs, emptySetOfOrganismParts); + + assertThat(cellTypes).isEmpty(); + } + + @Test + void whenInvalidCellIdsAndNoOrganismPartsProvidedReturnEmptySetOfCellTypes() { + var invalidCellIDs = + ImmutableSet.of("invalid-cellID-1", "invalid-cellID-2", "invalid-cellID-3"); + ImmutableSet emptySetOfOrganismParts = ImmutableSet.of(); + + var cellTypes = subject.searchCellTypes(invalidCellIDs, emptySetOfOrganismParts); + + assertThat(cellTypes).isEmpty(); + } + + @Test + void whenOnlyValidCellIdsButNoOrganismPartsProvidedReturnSetOfCellTypes() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(10))); + ImmutableSet emptySetOfOrganismParts = ImmutableSet.of(); + + var cellTypes = subject.searchCellTypes(randomListOfCellIDs, emptySetOfOrganismParts); + + assertThat(cellTypes.size()).isGreaterThan(0); + } + + @Test + void whenValidCellIdsButInvalidOrganismPartsProvidedReturnEmptySetOfCellTypes() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(10))); + ImmutableSet invalidOrganismParts = ImmutableSet.of("invalid-cellType-1", "invalid-cellType-2"); + + var cellTypes = subject.searchCellTypes(randomListOfCellIDs, invalidOrganismParts); + + assertThat(cellTypes).isEmpty(); + } + + @Test + void whenValidCellIdsAndValidProvidedReturnSetOfCellTypes() { + var randomListOfCellIDs = + ImmutableSet.copyOf( + new HashSet<>(jdbcUtils.fetchRandomListOfCells(3))); + ImmutableSet organismParts = solrUtils.fetchedRandomOrganismPartsByCellIDs( + randomListOfCellIDs, 1); + + var cellTypes = subject.searchCellTypes(randomListOfCellIDs, organismParts); + + assertThat(cellTypes.size()).isGreaterThan(0); + } } From 18cef2e6c15c3c419c58e9b4910c91adb48d8a82 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Fri, 4 Aug 2023 11:33:16 +0100 Subject: [PATCH 11/13] Implement CellTypeSearchService --- .../celltype/CellTypeSearchService.java | 29 +++++ .../celltype/CellTypeSearchServiceTest.java | 105 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchService.java create mode 100644 app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchServiceTest.java diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchService.java b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchService.java new file mode 100644 index 000000000..b06c536c8 --- /dev/null +++ b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchService.java @@ -0,0 +1,29 @@ +package uk.ac.ebi.atlas.search.celltype; + +import com.google.common.collect.ImmutableSet; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import uk.ac.ebi.atlas.search.GeneSearchService; + +@Component +@RequiredArgsConstructor +public class CellTypeSearchService { + + private final CellTypeSearchDao cellTypeSearchDao; + private final GeneSearchService geneSearchService; + + private static final Logger LOGGER = LoggerFactory.getLogger(CellTypeSearchService.class); + + public ImmutableSet search(ImmutableSet geneIds, ImmutableSet organismParts) { + if (geneIds.isEmpty()) { + LOGGER.warn("Can't query for organism part as no gene IDs has given."); + return ImmutableSet.of(); + } + + LOGGER.info("Searching organism parts for this gene ids: {}", geneIds.asList()); + + return cellTypeSearchDao.searchCellTypes(geneSearchService.getCellIdsFromGeneIds(geneIds), organismParts); + } +} diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchServiceTest.java b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchServiceTest.java new file mode 100644 index 000000000..3979f4fc5 --- /dev/null +++ b/app/src/test/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchServiceTest.java @@ -0,0 +1,105 @@ +package uk.ac.ebi.atlas.search.celltype; + +import com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import uk.ac.ebi.atlas.search.GeneSearchService; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class CellTypeSearchServiceTest { + + @Mock + private CellTypeSearchDao cellTypeSearchDao; + + @Mock + private GeneSearchService geneSearchService; + + private CellTypeSearchService subject; + + @BeforeEach + void setup() { + subject = new CellTypeSearchService(cellTypeSearchDao, geneSearchService); + } + + @Test + void whenEmptySetOfGeneIdsAndOrganismPartsProvidedReturnsEmptySetOfCellTypes() { + ImmutableSet emptySetOfGeneIds = ImmutableSet.of(); + ImmutableSet emptySetOfOrganismParts = ImmutableSet.of(); + + var cellTypes = subject.search(emptySetOfGeneIds, emptySetOfOrganismParts); + + assertThat(cellTypes).isEmpty(); + } + + @Test + void whenNonExistentGeneIdsAndEmptySetOfOrganismPartsProvidedReturnsEmptySetOfCellTypes() { + var nonExistentGeneId = "nonExistentGeneId"; + var setOfNonExistentGeneIds = ImmutableSet.of(nonExistentGeneId); + ImmutableSet emptySetOfOrganismParts = ImmutableSet.of(); + + when(geneSearchService.getCellIdsFromGeneIds(setOfNonExistentGeneIds)) + .thenReturn(ImmutableSet.of()); + when(cellTypeSearchDao.searchCellTypes(ImmutableSet.of(), emptySetOfOrganismParts)) + .thenReturn(ImmutableSet.of()); + + var cellTypes = subject.search(setOfNonExistentGeneIds, emptySetOfOrganismParts); + + assertThat(cellTypes).isEmpty(); + } + + @Test + void whenValidGeneIdsAndEmptySetOfOrganismPartsProvidedReturnsSetOfCellTypes() { + 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 emptySetOfOrganismParts = ImmutableSet.of(); + + var expectedCellType = "root cortex 7"; + + when(geneSearchService.getCellIdsFromGeneIds(geneIds)) + .thenReturn(cellIds); + when(cellTypeSearchDao.searchCellTypes(cellIds, emptySetOfOrganismParts)) + .thenReturn(ImmutableSet.of(expectedCellType)); + + var cellTypes = subject.search(geneIds, emptySetOfOrganismParts); + + assertThat(cellTypes).contains(expectedCellType); + } + + @Test + void whenValidGeneIDsAndOrganismPartsProvidedReturnsSetOfCellTypes() { + var existingGeneId1 = "ExistingGeneId1"; + var existingGeneId2 = "ExistingGeneId2"; + var geneIds = ImmutableSet.of(existingGeneId1, existingGeneId2); + var existingCellId1 = "ExistingCellId1"; + var existingCellId2 = "ExistingCellId2"; + var cellIds = ImmutableSet.of(existingCellId1, existingCellId2); + var existingCellType1 = "ExistingCellType1"; + var existingCellType2 = "ExistingCellType2"; + var organismParts = ImmutableSet.of(existingCellType1, existingCellType2); + + var expectedCellType = "root cortex 7"; + + when(geneSearchService.getCellIdsFromGeneIds(geneIds)) + .thenReturn(cellIds); + when(cellTypeSearchDao.searchCellTypes(cellIds, organismParts)) + .thenReturn(ImmutableSet.of(expectedCellType)); + + var cellTypes = subject.search(geneIds, organismParts); + + assertThat(cellTypes).contains(expectedCellType); + } +} From 24f6c443e224031c32aa94231fd6fe2cf04b1735 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Fri, 4 Aug 2023 16:04:10 +0100 Subject: [PATCH 12/13] Use new CellTypeSearchService in JsonGeneSearchController --- .../atlas/search/JsonGeneSearchController.java | 9 ++++++--- .../search/JsonGeneSearchControllerIT.java | 17 ++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) 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 fe4c3ddb4..316c08164 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 @@ -17,7 +17,7 @@ import uk.ac.ebi.atlas.controllers.JsonExceptionHandlingController; import uk.ac.ebi.atlas.experimentpage.ExperimentAttributesService; import uk.ac.ebi.atlas.model.experiment.singlecell.SingleCellBaselineExperiment; -import uk.ac.ebi.atlas.search.analytics.AnalyticsSearchService; +import uk.ac.ebi.atlas.search.celltype.CellTypeSearchService; import uk.ac.ebi.atlas.search.geneids.GeneIdSearchService; import uk.ac.ebi.atlas.search.geneids.QueryParsingException; import uk.ac.ebi.atlas.search.organismpart.OrganismPartSearchService; @@ -48,8 +48,8 @@ public class JsonGeneSearchController extends JsonExceptionHandlingController { private final ExperimentTrader experimentTrader; private final ExperimentAttributesService experimentAttributesService; - private final AnalyticsSearchService analyticsSearchService; private final OrganismPartSearchService organismPartSearchService; + private final CellTypeSearchService cellTypeSearchService; private final SpeciesSearchService speciesSearchService; @GetMapping(value = "/json/search", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @@ -267,7 +267,10 @@ public Set getCellTypeBySearchTerm(@RequestParam MultiValueMap(); when(geneIdSearchServiceMock.getGeneQueryByRequestParams(requestParams)) @@ -277,7 +276,7 @@ void whenSearchTermIsNotFoundAnyGeneIdsCellTypeSearchReturnsEmptySet() { .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(ImmutableSet.of())); - when(analyticsSearchServiceMock.searchCellType(ImmutableSet.of())) + when(cellTypeSearchService.search(ImmutableSet.of(), ImmutableSet.of())) .thenReturn(ImmutableSet.of()); var emptyCellTypeSet = subject.getCellTypeBySearchTerm(requestParams); @@ -299,7 +298,7 @@ void whenSearchTermIsFoundButNoRelatedCellIdsThenCellTypeSearchReturnsEmptySet() .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(geneIdsFromService)); - when(analyticsSearchServiceMock.searchCellType(geneIdsFromService)) + when(cellTypeSearchService.search(geneIdsFromService, ImmutableSet.of())) .thenReturn(ImmutableSet.of()); var emptyCellTypeSet = subject.getCellTypeBySearchTerm(requestParams); @@ -322,7 +321,7 @@ void whenSearchTermIsFoundAndThereAreRelatedCellIdsThenReturnsCellTypes() { .thenReturn(geneQuery); when(geneIdSearchServiceMock.search(geneQuery)) .thenReturn(Optional.of(geneIdsFromService)); - when(analyticsSearchServiceMock.searchCellType(geneIdsFromService)) + when(cellTypeSearchService.search(geneIdsFromService, ImmutableSet.of())) .thenReturn(ImmutableSet.of(expectedCellType)); var actualCellTypes = subject.getCellTypeBySearchTerm(requestParams); From af1844c4e5f30b4ef15504a8e3512de5a894ebe4 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Tue, 19 Sep 2023 16:45:49 +0100 Subject: [PATCH 13/13] Remove null value from cell type search results --- .../ebi/atlas/search/celltype/CellTypeSearchDao.java | 1 + .../atlas/search/JsonGeneSearchControllerWIT.java | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java index 03fcd4830..3f9dc1018 100644 --- a/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java +++ b/app/src/main/java/uk/ac/ebi/atlas/search/celltype/CellTypeSearchDao.java @@ -199,6 +199,7 @@ private SearchStreamBuilder getStreamBuilder private ImmutableSet getCellTypeFromStreamQuery(UniqueStreamBuilder uniqueCellTypeStreamBuilder) { try (TupleStreamer tupleStreamer = TupleStreamer.of(uniqueCellTypeStreamBuilder.build())) { return tupleStreamer.get() + .filter(tuple -> !tuple.getFields().isEmpty()) .map(tuple -> tuple.getString(CTW_CELL_TYPE.name())) .collect(toImmutableSet() ); diff --git a/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerWIT.java b/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerWIT.java index bfe032c5a..94393d9f0 100644 --- a/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerWIT.java +++ b/app/src/test/java/uk/ac/ebi/atlas/search/JsonGeneSearchControllerWIT.java @@ -1,6 +1,7 @@ package uk.ac.ebi.atlas.search; import com.google.common.collect.ImmutableSet; +import org.hamcrest.core.IsNull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -29,6 +30,7 @@ import javax.inject.Inject; import javax.sql.DataSource; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; @@ -346,4 +348,14 @@ void whenSearchTermExistsInDBThenReturnsSetOfCellType() throws Exception { .andExpect(jsonPath("$", hasSize(equalTo(expectedCellTypes.size())))) .andExpect(jsonPath("$", containsInAnyOrder(expectedCellTypes.toArray()))); } + + @Test + void whenGivenExistingSymbolReturnedCellTypesNotContainsNullValue() throws Exception { + var symbolValue = "CFTR"; + + this.mockMvc.perform(get("/json/gene-search/cell-types").param("symbol", symbolValue)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$", not(contains(IsNull.nullValue())))); + } }