Skip to content

Commit

Permalink
feat(openchallenges): filter EDAM concepts by sections (Sage-Bionetwo…
Browse files Browse the repository at this point in the history
  • Loading branch information
tschaffter authored Apr 18, 2024
1 parent ce781bd commit 560fbf6
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/Eda
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamConceptsPageDto.java
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamDataDto.java
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamOperationDto.java
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/EdamSectionDto.java
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/PageMetadataDto.java
src/main/java/org/sagebionetworks/openchallenges/challenge/service/model/dto/SimpleChallengePlatformDto.java
src/main/resources/openapi.yaml
6 changes: 5 additions & 1 deletion apps/openchallenges/challenge-service/requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,8 @@ GET {{basePath}}/edamConcepts

### List the EDAM concepts that match the space-separated search terms "sequence".

GET {{basePath}}/edamConcepts?searchTerms=sequence
GET {{basePath}}/edamConcepts?searchTerms=sequence

### List the EDAM concepts that belong to the "data" or "format" section.

GET {{basePath}}/edamConcepts?sections=data,format
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.sagebionetworks.openchallenges.challenge.service.model.dto.ChallengeSortDto;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.ChallengeStatusDto;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.ChallengeSubmissionTypeDto;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamSectionDto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
Expand Down Expand Up @@ -105,4 +106,14 @@ public ChallengeSubmissionTypeDto convert(String source) {
}
};
}

@Bean
Converter<String, EdamSectionDto> edamSectionConverter() {
return new Converter<String, EdamSectionDto>() {
@Override
public EdamSectionDto convert(String source) {
return EdamSectionDto.fromValue(source);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.annotation.Generated;
import javax.validation.Valid;
import javax.validation.constraints.*;

/** An EDAM concept search query. */
Expand All @@ -24,6 +27,10 @@ public class EdamConceptSearchQueryDto {
@JsonProperty("searchTerms")
private String searchTerms;

@JsonProperty("sections")
@Valid
private List<EdamSectionDto> sections = null;

public EdamConceptSearchQueryDto pageNumber(Integer pageNumber) {
this.pageNumber = pageNumber;
return this;
Expand Down Expand Up @@ -90,6 +97,37 @@ public void setSearchTerms(String searchTerms) {
this.searchTerms = searchTerms;
}

public EdamConceptSearchQueryDto sections(List<EdamSectionDto> sections) {
this.sections = sections;
return this;
}

public EdamConceptSearchQueryDto addSectionsItem(EdamSectionDto sectionsItem) {
if (this.sections == null) {
this.sections = new ArrayList<>();
}
this.sections.add(sectionsItem);
return this;
}

/**
* An array of EDAM sections (sub-ontologies) used to filter the results.
*
* @return sections
*/
@Valid
@Schema(
name = "sections",
description = "An array of EDAM sections (sub-ontologies) used to filter the results.",
required = false)
public List<EdamSectionDto> getSections() {
return sections;
}

public void setSections(List<EdamSectionDto> sections) {
this.sections = sections;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -101,12 +139,13 @@ public boolean equals(Object o) {
EdamConceptSearchQueryDto edamConceptSearchQuery = (EdamConceptSearchQueryDto) o;
return Objects.equals(this.pageNumber, edamConceptSearchQuery.pageNumber)
&& Objects.equals(this.pageSize, edamConceptSearchQuery.pageSize)
&& Objects.equals(this.searchTerms, edamConceptSearchQuery.searchTerms);
&& Objects.equals(this.searchTerms, edamConceptSearchQuery.searchTerms)
&& Objects.equals(this.sections, edamConceptSearchQuery.sections);
}

@Override
public int hashCode() {
return Objects.hash(pageNumber, pageSize, searchTerms);
return Objects.hash(pageNumber, pageSize, searchTerms, sections);
}

@Override
Expand All @@ -116,6 +155,7 @@ public String toString() {
sb.append(" pageNumber: ").append(toIndentedString(pageNumber)).append("\n");
sb.append(" pageSize: ").append(toIndentedString(pageSize)).append("\n");
sb.append(" searchTerms: ").append(toIndentedString(searchTerms)).append("\n");
sb.append(" sections: ").append(toIndentedString(sections)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.sagebionetworks.openchallenges.challenge.service.model.dto;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.*;
import javax.annotation.Generated;
import javax.validation.constraints.*;

/** The EDAM section (sub-ontology). */
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public enum EdamSectionDto {
DATA("data"),

FORMAT("format"),

IDENTIFIER("identifier"),

OPERATION("operation"),

TOPIC("topic");

private String value;

EdamSectionDto(String value) {
this.value = value;
}

@JsonValue
public String getValue() {
return value;
}

@Override
public String toString() {
return String.valueOf(value);
}

@JsonCreator
public static EdamSectionDto fromValue(String value) {
for (EdamSectionDto b : EdamSectionDto.values()) {
if (b.value.equals(value)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.ValueBridgeRef;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField;
import org.sagebionetworks.openchallenges.challenge.service.model.search.EdamSectionValueBridge;

@Entity
@Table(name = "edam_concept")
Expand All @@ -29,6 +32,9 @@ public class EdamConceptEntity {

@Column(name = "class_id", nullable = false)
@FullTextField(name = "class_id")
@KeywordField(
name = "section",
valueBridge = @ValueBridgeRef(type = EdamSectionValueBridge.class))
private String classId;

@Column(name = "preferred_label", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamConceptSearchQueryDto;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamSectionDto;
import org.sagebionetworks.openchallenges.challenge.service.model.entity.EdamConceptEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand Down Expand Up @@ -38,6 +39,9 @@ private SearchResult<EdamConceptEntity> getSearchResult(
if (query.getSearchTerms() != null && !query.getSearchTerms().isBlank()) {
predicates.add(getSearchTermsPredicate(pf, query, fields));
}
if (query.getSections() != null && !query.getSections().isEmpty()) {
predicates.add(getEdamSectionsPredicate(pf, query));
}

SearchPredicate topLevelPredicate = buildTopLevelPredicate(pf, predicates);

Expand All @@ -64,6 +68,24 @@ private SearchPredicate getSearchTermsPredicate(
.toPredicate();
}

/**
* Searches the EDAM concepts whose section is in the list of sections specified.
*
* @param pf
* @param query
* @return
*/
private SearchPredicate getEdamSectionsPredicate(
SearchPredicateFactory pf, EdamConceptSearchQueryDto query) {
return pf.bool(
b -> {
for (EdamSectionDto section : query.getSections()) {
b.should(pf.match().field("section").matching(section.toString()));
}
})
.toPredicate();
}

/**
* Combines the search predicates.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sagebionetworks.openchallenges.challenge.service.model.search;

import org.hibernate.search.mapper.pojo.bridge.ValueBridge;
import org.hibernate.search.mapper.pojo.bridge.runtime.ValueBridgeToIndexedValueContext;
import org.sagebionetworks.openchallenges.challenge.service.model.dto.EdamSectionDto;

public class EdamSectionValueBridge implements ValueBridge<String, String> {

@Override
public String toIndexedValue(String value, ValueBridgeToIndexedValueContext context) {
for (EdamSectionDto section : EdamSectionDto.values()) {
// The first condition is used to map EDAM concept class IDs to concept sections when new
// concept are created (e.g. during mass indexing). This value bridge is also called when
// sending search queries to Elasticsearch, which is why the second condition is required to
// map the user input to an existing EDAM concept.
if (value.contains("/" + section.getValue() + "_") || section.getValue().equals(value)) {
return section.getValue();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@

<include resource="logback-common.xml" />

<logger name="org.hibernate" level="info" />
<logger name="org.hibernate.search.query" level="info" />

<!-- <logger name="org.apache.kafka" level="debug" /> -->
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,16 @@ components:
type: object
x-java-class-annotations:
- '@lombok.Builder'
EdamSection:
description: The EDAM section (sub-ontology).
enum:
- data
- format
- identifier
- operation
- topic
example: data
type: string
EdamConceptSearchQuery:
description: An EDAM concept search query.
properties:
Expand All @@ -1043,6 +1053,12 @@ components:
description: A string of search terms used to filter the results.
example: sequence image
type: string
sections:
description: An array of EDAM sections (sub-ontologies) used to filter the
results.
items:
$ref: '#/components/schemas/EdamSection'
type: array
type: object
EdamConceptId:
description: The unique identifier of the EDAM concept.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ model/edamConceptsPage.ts
model/edamConceptsPageAllOf.ts
model/edamData.ts
model/edamOperation.ts
model/edamSection.ts
model/image.ts
model/imageAspectRatio.ts
model/imageHeight.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { EdamSection } from './edamSection';


/**
Expand All @@ -27,5 +28,9 @@ export interface EdamConceptSearchQuery {
* A string of search terms used to filter the results.
*/
searchTerms?: string;
/**
* An array of EDAM sections (sub-ontologies) used to filter the results.
*/
sections?: Array<EdamSection>;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* OpenChallenges REST API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/


/**
* The EDAM section (sub-ontology).
*/
export type EdamSection = 'data' | 'format' | 'identifier' | 'operation' | 'topic';

export const EdamSection = {
Data: 'data' as EdamSection,
Format: 'format' as EdamSection,
Identifier: 'identifier' as EdamSection,
Operation: 'operation' as EdamSection,
Topic: 'topic' as EdamSection
};

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from './edamConceptsPage';
export * from './edamConceptsPageAllOf';
export * from './edamData';
export * from './edamOperation';
export * from './edamSection';
export * from './image';
export * from './imageAspectRatio';
export * from './imageHeight';
Expand Down
15 changes: 15 additions & 0 deletions libs/openchallenges/api-description/build/challenge.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,16 @@ components:
- challengePlatforms
x-java-class-annotations:
- '@lombok.Builder'
EdamSection:
description: The EDAM section (sub-ontology).
type: string
enum:
- data
- format
- identifier
- operation
- topic
example: data
EdamConceptSearchQuery:
type: object
description: An EDAM concept search query.
Expand All @@ -741,6 +751,11 @@ components:
description: A string of search terms used to filter the results.
type: string
example: sequence image
sections:
description: An array of EDAM sections (sub-ontologies) used to filter the results.
type: array
items:
$ref: '#/components/schemas/EdamSection'
EdamConceptId:
description: The unique identifier of the EDAM concept.
type: integer
Expand Down
Loading

0 comments on commit 560fbf6

Please sign in to comment.