From ccfe8ff27b76a01ad8481746c6953883fa1fa0f8 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 1 Feb 2023 14:51:21 +0000 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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 06bdccf1fb358ac25e5b2f6fe4bbbf7b394672c6 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 2 Aug 2023 17:17:53 +0100 Subject: [PATCH 07/11] 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 08/11] 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 fbf3dea1a9ed3c279628cf75acb3c06975f160c7 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Wed, 25 Oct 2023 17:40:21 +0100 Subject: [PATCH 09/11] Clean the code that gets the matching gene ids --- .../search/JsonGeneSearchController.java | 24 +++++++++---------- 1 file changed, 12 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..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 @@ -28,7 +28,6 @@ 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; @@ -132,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 = - (Objects.requireNonNull(geneIds.orElse(null)).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( @@ -180,11 +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 experimentAttributes = ImmutableMap.builder().putAll( @@ -207,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( @@ -284,7 +284,7 @@ public ImmutableSet 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()) From 806900a17b89855b0cf39df08bd5dc59b0142234 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Mon, 30 Oct 2023 14:59:44 +0000 Subject: [PATCH 10/11] Update submodules --- app/src/main/javascript/modules/experiments-summary-panel | 2 +- app/src/main/javascript/modules/species-summary-panel | 2 +- atlas-web-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/javascript/modules/experiments-summary-panel b/app/src/main/javascript/modules/experiments-summary-panel index dec725bb9..d88ce03cd 160000 --- a/app/src/main/javascript/modules/experiments-summary-panel +++ b/app/src/main/javascript/modules/experiments-summary-panel @@ -1 +1 @@ -Subproject commit dec725bb9e336b90aeb4e459b9891f9a14fd7a73 +Subproject commit d88ce03cde100799d8f7a0d17cac67538aa1d13f diff --git a/app/src/main/javascript/modules/species-summary-panel b/app/src/main/javascript/modules/species-summary-panel index bd3b944e9..b9edbdbbc 160000 --- a/app/src/main/javascript/modules/species-summary-panel +++ b/app/src/main/javascript/modules/species-summary-panel @@ -1 +1 @@ -Subproject commit bd3b944e91e75600889f14e71a152aca2e9a91fb +Subproject commit b9edbdbbcc661cc30d147818e8529f339f597f19 diff --git a/atlas-web-core b/atlas-web-core index f0bd77685..3856fc9b9 160000 --- a/atlas-web-core +++ b/atlas-web-core @@ -1 +1 @@ -Subproject commit f0bd77685c933e2aa6e70b212335f7314489ccbc +Subproject commit 3856fc9b9d91ca6b66c2ed0dd1d9394124f96360 From d15dba73b54eefbffe0e760b969ea974ff936e31 Mon Sep 17 00:00:00 2001 From: Karoly Erdos Date: Mon, 30 Oct 2023 15:06:18 +0000 Subject: [PATCH 11/11] Update atlas-web-core to the latest version --- atlas-web-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlas-web-core b/atlas-web-core index 3856fc9b9..2dcf1d8e0 160000 --- a/atlas-web-core +++ b/atlas-web-core @@ -1 +1 @@ -Subproject commit 3856fc9b9d91ca6b66c2ed0dd1d9394124f96360 +Subproject commit 2dcf1d8e0e19f58be6971a3f642a50e2da3c1023