Skip to content

Commit

Permalink
Merge pull request #19 from TheJacksonLaboratory/feature/intersect
Browse files Browse the repository at this point in the history
Feature/intersect
  • Loading branch information
iimpulse authored May 22, 2024
2 parents 0cc99da + 8db27ec commit 789257b
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 26 deletions.
2 changes: 1 addition & 1 deletion oan-etl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.jax.oan</groupId>
<artifactId>ontology-annotation-network</artifactId>
<version>1.0.3-SNAPSHOT</version>
<version>1.0.4</version>
</parent>

<artifactId>oan-etl</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion oan-model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jax.oan</groupId>
<artifactId>ontology-annotation-network</artifactId>
<version>1.0.3-SNAPSHOT</version>
<version>1.0.4</version>
</parent>

<artifactId>oan-model</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static SupportedEntity from(TermId termId) {
return SupportedEntity.UNKNOWN;
}

public static boolean isSupportedDownload(SupportedEntity entity, SupportedEntity type){
public static boolean isLinkedType(SupportedEntity entity, SupportedEntity type){
return switch (entity) {
case PHENOTYPE -> (type.equals(DISEASE) || type.equals(GENE));
case DISEASE -> (type.equals(PHENOTYPE) || type.equals(GENE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ private static Stream<Arguments> from() {

@ParameterizedTest
@MethodSource
void isSupportedDownload(SupportedEntity entity, SupportedEntity association) {
assertTrue(SupportedEntity.isSupportedDownload(entity, association));
assertFalse(SupportedEntity.isSupportedDownload(entity, entity));
void isLinkedType(SupportedEntity entity, SupportedEntity association) {
assertTrue(SupportedEntity.isLinkedType(entity, association));
assertFalse(SupportedEntity.isLinkedType(entity, entity));
}

private static Stream<Arguments> isSupportedDownload() {
private static Stream<Arguments> isLinkedType() {
return Stream.of(
Arguments.of(SupportedEntity.GENE, SupportedEntity.PHENOTYPE),
Arguments.of(SupportedEntity.PHENOTYPE, SupportedEntity.DISEASE),
Expand Down
7 changes: 1 addition & 6 deletions oan-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jax.oan</groupId>
<artifactId>ontology-annotation-network</artifactId>
<version>1.0.3-SNAPSHOT</version>
<version>1.0.4</version>
</parent>

<artifactId>oan-rest</artifactId>
Expand Down Expand Up @@ -78,11 +78,6 @@
<artifactId>javax.ws.rs-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micronaut.test</groupId>
<artifactId>micronaut-test-junit5</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions oan-rest/src/main/java/org/jax/oan/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
@OpenAPIDefinition(
info = @Info(
title = "ontology-annotation-network",
version = "1.0.3-SNAPSHOT",
version = "1.0.4",
description = "A restful service for access to the ontology annotation network.",
contact = @Contact(name = "Michael Gargano", email = "[email protected]")
), servers = {@Server(url = "https://ontology.jax.org/api/network", description = "Production Server URL")
// @Server(url = "http://localhost:8080/api/network", description = "Development Server URL")
// @Server(url = "http://localhost:8080/api/network", description = "Development Server URL")
}
)
public class Application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.micronaut.http.exceptions.HttpStatusException;
import io.micronaut.http.server.types.files.SystemFile;
import io.micronaut.serde.annotation.SerdeImport;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import org.jax.oan.core.*;
import org.jax.oan.exception.OntologyAnnotationNetworkException;
Expand Down Expand Up @@ -84,7 +83,7 @@ public SystemFile download(
TermId termId = TermId.of(id);
SupportedEntity entity = SupportedEntity.from(termId);
SupportedEntity downloadType = SupportedEntity.valueOf(type.toUpperCase());
if (SupportedEntity.isSupportedDownload(entity, downloadType)){
if (SupportedEntity.isLinkedType(entity, downloadType)){
return this.downloadService.associations(termId, entity, downloadType);
} else {
throw new HttpStatusException(HttpStatus.BAD_REQUEST, String.format("Downloading %s associations for %s is not supported.", entity, termId.getValue()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
package org.jax.oan.controller;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;
import io.micronaut.http.annotation.QueryValue;
import io.micronaut.http.exceptions.HttpStatusException;
import io.micronaut.http.server.types.files.SystemFile;
import io.micronaut.serde.annotation.SerdeImport;
import io.swagger.v3.oas.annotations.media.Schema;
import org.jax.oan.core.Disease;
import org.jax.oan.core.SearchDto;
import org.jax.oan.core.SupportedEntity;
import org.jax.oan.exception.OntologyAnnotationNetworkRuntimeException;
import org.jax.oan.service.DiseaseService;
import org.jax.oan.service.SearchService;
import org.monarchinitiative.phenol.base.PhenolRuntimeException;
import org.monarchinitiative.phenol.ontology.data.TermId;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

@Controller("/search")
@SerdeImport(SearchDto.class)
@SerdeImport(Disease.class)
public class SearchController {
private SearchService searchService;
private final SearchService searchService;
private final DiseaseService diseaseService;

public SearchController(SearchService searchService) {
public SearchController(SearchService searchService, DiseaseService diseaseService) {
this.searchService = searchService;
this.diseaseService = diseaseService;
}

@Get(uri="/{entity}", produces="application/json")
Expand All @@ -34,7 +50,34 @@ public HttpResponse<?> searchEntity(@Schema(minLength = 1, maxLength = 20, type
} else if (entity.equalsIgnoreCase("DISEASE")){
return HttpResponse.ok(this.searchService.searchDisease(q.toUpperCase(), page, limit));
} else {
return HttpResponse.noContent();
return HttpResponse.badRequest();
}
}


/**
* This is our base controller for annotations that deals with different ontology term types
* and returns a defined annotation schema.
* @param entity the entity you care about with your list of phenotypes
* @param p the list of comma-seperated phenotype(hp) term ids
* @return an http response with the specific annotation schema based on the type
* @throws OntologyAnnotationNetworkRuntimeException which will be a 500
*/
@Get(uri="/{entity}/intersect", produces="application/json")
public HttpResponse<?> intersect(
@Schema(minLength = 1, maxLength = 20, type = "string", pattern = ".*", format = "string") @PathVariable String entity,
@Schema(minLength = 1, maxLength = 20000, type = "string", pattern = ".*", format = "string") @QueryValue String p) {
try {
Collection<TermId> terms = Arrays.stream(p.split(",")).map(TermId::of).toList();
SupportedEntity target = SupportedEntity.valueOf(entity.toUpperCase());
if (SupportedEntity.isLinkedType(SupportedEntity.PHENOTYPE, target)){
return HttpResponse.ok(this.diseaseService.findIntersectingByPhenotypes(terms));
} else {
throw new HttpStatusException(HttpStatus.BAD_REQUEST, String.format("Intersecting %s associations for your phenotypes is not supported.", entity));
}
} catch(PhenolRuntimeException | OntologyAnnotationNetworkRuntimeException e){
throw new HttpStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}

}
30 changes: 27 additions & 3 deletions oan-rest/src/main/java/org/jax/oan/service/DiseaseService.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package org.jax.oan.service;

import io.micronaut.serde.annotation.SerdeImport;
import jakarta.inject.Singleton;
import org.jax.oan.core.*;
import org.jax.oan.exception.OntologyAnnotationNetworkException;
import org.jax.oan.repository.DiseaseRepository;
import org.jax.oan.repository.PhenotypeRepository;
import org.monarchinitiative.phenol.ontology.data.TermId;

import java.util.Collection;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

@Singleton
@SerdeImport(Disease.class)
public class DiseaseService {

private final DiseaseRepository diseaseRepository;
private final PhenotypeRepository phenotypeRepository;

public DiseaseService(DiseaseRepository diseaseRepository) {
public DiseaseService(DiseaseRepository diseaseRepository, PhenotypeRepository phenotypeRepository) {
this.diseaseRepository = diseaseRepository;
this.phenotypeRepository = phenotypeRepository;
}

public DiseaseAnnotationDto findAll(TermId termId) throws OntologyAnnotationNetworkException {
Expand All @@ -33,4 +37,24 @@ public DiseaseAnnotationDto findAll(TermId termId) throws OntologyAnnotationNetw
}
throw new OntologyAnnotationNetworkException(String.format("Could not find disease with id %s", termId.getValue()));
}

public Collection<Disease> findIntersectingByPhenotypes(Collection<TermId> termIds){
List<Disease> intersecting = new ArrayList<>();
for (TermId id: termIds){
try {
if (intersecting.isEmpty()){
intersecting.addAll(phenotypeRepository.findDiseasesByTerm(id));
} else {
Collection<Disease> diseases = phenotypeRepository.findDiseasesByTerm(id);
intersecting = intersecting.stream().distinct()
.filter(diseases::contains)
.collect(Collectors.toList());
}

} catch (Exception ex) {
return Collections.emptyList();
}
}
return intersecting.stream().distinct().toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.jax.oan.controller;

import io.micronaut.runtime.EmbeddedApplication;
import io.micronaut.test.annotation.MockBean;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.restassured.specification.RequestSpecification;
import jakarta.inject.Inject;
import org.jax.oan.core.Disease;
import org.jax.oan.core.Gene;
import org.jax.oan.core.SearchDto;
import org.jax.oan.service.DiseaseService;
import org.jax.oan.service.SearchService;
import org.junit.jupiter.api.Test;
import org.monarchinitiative.phenol.ontology.data.TermId;

import java.util.List;

import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@MicronautTest
public class SearchControllerTest {
@Inject
EmbeddedApplication<?> application;

@Inject
DiseaseService diseaseService;

@Inject
SearchService searchService;

@Test
void positive_search_by_gene(RequestSpecification spec){
when(searchService.searchGene("TP53", 0,10))
.thenReturn(new SearchDto(List.of(new Gene(TermId.of("NCBIGene:093232"), "tp53"),
new Gene(TermId.of("NCBIGene:093234"), "tp53b")), 2));
spec.when().get("/api/network/search/gene?q=tp53").then()
.statusCode(200)
.body("results.id", hasItems("NCBIGene:093234", "NCBIGene:093232"))
.body("totalCount", equalTo(2));
}

@Test
void positive_search_by_disease(RequestSpecification spec){
when(searchService.searchDisease("MARFAN", 0,10))
.thenReturn(new SearchDto(List.of(
new Disease(TermId.of("OMIM:333333"), "Bad disease 1", "MONDO:000001", "no description"),
new Disease(TermId.of("OMIM:444444"), "Bad disease 2", "MONDO:000002", "funky description"),
new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")), 3));
spec.when().get("/api/network/search/disease?q=marfan").then()
.statusCode(200)
.body("results.id", hasItems("OMIM:333333", "OMIM:444444"))
.body("totalCount", equalTo(3));
}

@Test
void negative_search_by_bad_entity(RequestSpecification spec){
spec.when().get("/api/network/search/variant?q=chr1:60023-2300").then().statusCode(400);
spec.when().get("/api/network/search/phenotype?q=bighead").then().statusCode(400);
}


@Test
void postive_intersecting(RequestSpecification spec){
when(diseaseService.findIntersectingByPhenotypes(List.of(TermId.of("HP:333333"), TermId.of("HP:44444"))))
.thenReturn(List.of(
new Disease(TermId.of("OMIM:333333"), "Bad disease 1", "MONDO:000001", "no description"),
new Disease(TermId.of("OMIM:444444"), "Bad disease 2", "MONDO:000002", "funky description"),
new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")));
spec.when().get("/api/network/search/disease/intersect?p=HP:333333,HP:44444").then()
.statusCode(200)
.body("id", hasItems("OMIM:333333", "OMIM:444444"));
}

@Test
void negative_intersecting(RequestSpecification spec){
spec.when().get("/api/network/search/disease/intersect?q=chr1:60023-2300").then().statusCode(400);
spec.when().get("/api/network/search/phenotype?q=bighead").then().statusCode(400);
}

@MockBean(DiseaseService.class)
DiseaseService diseaseService() {
return mock(DiseaseService.class);
}

@MockBean(SearchService.class)
SearchService searchService() {
return mock(SearchService.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -70,7 +71,7 @@ void findPhenotypesByDisease() {
}

@Test
void findDisease(){
void findDiseases(){
Collection<Disease> diseases = diseaseRepository.findDiseases("bad");
List<Disease> expected = List.of(
new Disease(TermId.of("OMIM:555555"), "Bad disease", "MONDO:0000001", "Rare disease"),
Expand All @@ -79,4 +80,13 @@ void findDisease(){

assertTrue(diseases.containsAll(expected));
}

@Test
void findDiseaseById(){
Optional<Disease> disease = diseaseRepository.findDiseaseById(TermId.of("OMIM:555555"));
Disease expected = new Disease(TermId.of("OMIM:555555"), "Bad disease", "MONDO:0000001", "Rare disease");

assertTrue(disease.isPresent());
assertEquals(disease.get(), expected);
}
}
Loading

0 comments on commit 789257b

Please sign in to comment.