From 87e7f777cf9ac0fa953acdc4e2bd72b1b23f607f Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Mon, 24 Jun 2024 10:02:47 -0400 Subject: [PATCH] [ALS-6276] Search improvements --- db/schema.sql | 1 + pom.xml | 5 ++ .../dictionary/concept/ConceptRepository.java | 20 +++--- .../dictionary/concept/ConceptRowMapper.java | 26 +++++++- .../concept/model/CategoricalConcept.java | 6 +- .../concept/model/ContinuousConcept.java | 6 +- .../dictionary/facet/FacetRepository.java | 2 +- .../filter/FilterQueryGenerator.java | 24 +++++-- .../concept/ConceptControllerTest.java | 32 +++++----- .../concept/ConceptRepositoryTest.java | 64 +++++++++---------- .../concept/ConceptServiceTest.java | 6 +- .../dictionary/concept/model/ConceptTest.java | 19 +++++- 12 files changed, 136 insertions(+), 75 deletions(-) diff --git a/db/schema.sql b/db/schema.sql index 7d787dd..65fb0f6 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -37,6 +37,7 @@ CREATE TABLE dict.concept_node ( CONCEPT_TYPE VARCHAR(32) NOT NULL DEFAULT 'Interior', CONCEPT_PATH VARCHAR(10000) NOT NULL DEFAULT 'INVALID', PARENT_ID INT, + SEARCHABLE_FIELDS TSVECTOR, PRIMARY KEY (CONCEPT_NODE_ID), CONSTRAINT fk_parent FOREIGN KEY (PARENT_ID) REFERENCES dict.CONCEPT_NODE(CONCEPT_NODE_ID), CONSTRAINT fk_study FOREIGN KEY (DATASET_ID) REFERENCES dict.dataset(DATASET_ID) diff --git a/pom.xml b/pom.xml index ccad679..92114a3 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ spring-boot-starter-test test + + org.json + json + 20240303 + org.springframework.boot spring-boot-testcontainers diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepository.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepository.java index 6307c8e..3743710 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepository.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepository.java @@ -40,13 +40,15 @@ public List getConcepts(Filter filter, Pageable pageable) { concept_node.*, ds.REF as dataset, continuous_min.VALUE as min, continuous_max.VALUE as max, - categorical_values.VALUE as values + categorical_values.VALUE as values, + meta_description.VALUE AS description FROM concept_node LEFT JOIN dataset AS ds ON concept_node.dataset_id = ds.dataset_id - LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'MIN' - LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'MAX' - LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'VALUES' + LEFT JOIN concept_node_meta AS meta_description ON concept_node.concept_node_id = meta_description.concept_node_id AND meta_description.KEY = 'description' + LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' + LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' + LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE concept_node.concept_node_id IN """; QueryParamPair filterQ = filterGen.generateFilterQuery(filter, pageable); @@ -69,13 +71,15 @@ public Optional getConcept(String dataset, String conceptPath) { concept_node.*, ds.REF as dataset, continuous_min.VALUE as min, continuous_max.VALUE as max, - categorical_values.VALUE as values + categorical_values.VALUE as values, + meta_description.VALUE AS description FROM concept_node LEFT JOIN dataset AS ds ON concept_node.dataset_id = ds.dataset_id - LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'MIN' - LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'MAX' - LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'VALUES' + LEFT JOIN concept_node_meta AS meta_description ON concept_node.concept_node_id = meta_description.concept_node_id AND meta_description.KEY = 'description' + LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' + LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' + LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' WHERE concept_node.concept_path = :conceptPath AND ds.REF = :dataset diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRowMapper.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRowMapper.java index b5671bd..fc632d0 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRowMapper.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRowMapper.java @@ -1,6 +1,8 @@ package edu.harvard.dbmi.avillach.dictionary.concept; import edu.harvard.dbmi.avillach.dictionary.concept.model.*; +import org.json.JSONArray; +import org.json.JSONException; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -24,7 +26,7 @@ public Concept mapRow(ResultSet rs, int rowNum) throws SQLException { private CategoricalConcept mapCategorical(ResultSet rs) throws SQLException { return new CategoricalConcept( rs.getString("concept_path"), rs.getString("name"), - rs.getString("display"), rs.getString("dataset"), + rs.getString("display"), rs.getString("dataset"), rs.getString("description"), rs.getString("values") == null ? List.of() : List.of(rs.getString("values").split(",")), null, null @@ -34,9 +36,27 @@ private CategoricalConcept mapCategorical(ResultSet rs) throws SQLException { private ContinuousConcept mapContinuous(ResultSet rs) throws SQLException { return new ContinuousConcept( rs.getString("concept_path"), rs.getString("name"), - rs.getString("display"), rs.getString("dataset"), - rs.getInt("min"), rs.getInt("max"), + rs.getString("display"), rs.getString("dataset"), rs.getString("description"), + parseMin(rs.getString("values")), parseMax(rs.getString("values")), null ); } + + private Integer parseMin(String valuesArr) { + try { + JSONArray arr = new JSONArray(valuesArr); + return arr.length() == 2 ? arr.getInt(0) : 0; + } catch (JSONException ex) { + return 0; + } + } + + private Integer parseMax(String valuesArr) { + try { + JSONArray arr = new JSONArray(valuesArr); + return arr.length() == 2 ? arr.getInt(1) : 0; + } catch (JSONException ex) { + return 0; + } + } } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/CategoricalConcept.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/CategoricalConcept.java index f700424..26b8485 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/CategoricalConcept.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/CategoricalConcept.java @@ -1,12 +1,13 @@ package edu.harvard.dbmi.avillach.dictionary.concept.model; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.annotation.Nullable; import java.util.List; import java.util.Map; public record CategoricalConcept( - String conceptPath, String name, String display, String dataset, + String conceptPath, String name, String display, String dataset, String description, List values, @@ -19,10 +20,11 @@ public record CategoricalConcept( ) implements Concept { public CategoricalConcept(CategoricalConcept core, Map meta) { - this(core.conceptPath, core.name, core.display, core.dataset, core.values, core.children, meta); + this(core.conceptPath, core.name, core.display, core.dataset, core.description, core.values, core.children, meta); } + @JsonProperty("type") @Override public ConceptType type() { return ConceptType.Categorical; diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ContinuousConcept.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ContinuousConcept.java index db338b3..3b6c93c 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ContinuousConcept.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ContinuousConcept.java @@ -1,20 +1,22 @@ package edu.harvard.dbmi.avillach.dictionary.concept.model; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.annotation.Nullable; import java.util.Map; public record ContinuousConcept( - String conceptPath, String name, String display, String dataset, + String conceptPath, String name, String display, String dataset, String description, @Nullable Integer min, @Nullable Integer max, Map meta ) implements Concept { public ContinuousConcept(ContinuousConcept core, Map meta) { - this(core.conceptPath, core.name, core.display, core.dataset, core.min, core.max, meta); + this(core.conceptPath, core.name, core.display, core.dataset, core.description, core.min, core.max, meta); } + @JsonProperty("type") @Override public ConceptType type() { return ConceptType.Continuous; diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepository.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepository.java index 5a9a738..2921dec 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepository.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetRepository.java @@ -46,7 +46,7 @@ public List getFacets(Filter filter) { facet LEFT JOIN facet_category ON facet_category.facet_category_id = facet.facet_category_id LEFT JOIN facet as parent_facet ON facet.parent_id = parent_facet.facet_id - LEFT JOIN ( + INNER JOIN ( SELECT count(*) as facet_count, inner_facet_q.facet_id AS inner_facet_id FROM diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/FilterQueryGenerator.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/FilterQueryGenerator.java index a159b0d..bfcfc59 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/FilterQueryGenerator.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/filter/FilterQueryGenerator.java @@ -34,9 +34,23 @@ public QueryParamPair generateFilterQuery(Filter filter, Pageable pageable) { if (StringUtils.hasText(filter.search())) { clauses.add(createSearchFilter(filter.search(), params)); } - if (clauses.isEmpty()) { - clauses = List.of("\tSELECT concept_node.concept_node_id FROM concept_node\n"); - } + clauses.add(""" + ( + SELECT + concept_node.concept_node_id + FROM + concept_node + LEFT JOIN concept_node_meta AS continuous_min ON concept_node.concept_node_id = continuous_min.concept_node_id AND continuous_min.KEY = 'min' + LEFT JOIN concept_node_meta AS continuous_max ON concept_node.concept_node_id = continuous_max.concept_node_id AND continuous_max.KEY = 'max' + LEFT JOIN concept_node_meta AS categorical_values ON concept_node.concept_node_id = categorical_values.concept_node_id AND categorical_values.KEY = 'values' + WHERE + continuous_min.value <> '' OR + continuous_max.value <> '' OR + categorical_values.value <> '' + ) + """ + ); + String query = "(\n" + String.join("\n\tINTERSECT\n", clauses) + "\n) ORDER BY concept_node_id\n"; if (pageable.isPaged()) { @@ -53,7 +67,7 @@ public QueryParamPair generateFilterQuery(Filter filter, Pageable pageable) { } private String createSearchFilter(String search, MapSqlParameterSource params) { - params.addValue("search", "%" + search + "%"); + params.addValue("search", search); return """ ( SELECT @@ -61,7 +75,7 @@ private String createSearchFilter(String search, MapSqlParameterSource params) { FROM concept_node WHERE - concept_node.concept_path LIKE :search + concept_node.searchable_fields @@ (phraseto_tsquery(:search)::text || ':*')::tsquery ) """; } diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java index b6baef6..878fe3f 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptControllerTest.java @@ -32,9 +32,9 @@ class ConceptControllerTest { @Test void shouldListConcepts() { List expected = List.of( - new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", List.of(), null, Map.of()), - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()), - new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", 0, 100, Map.of()) + new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", "foo!", List.of(), null, Map.of()), + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()), + new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", "foo!", 0, 100, Map.of()) ); Filter filter = new Filter( List.of(new Facet("questionare", "Questionare", "?", 1, null, "category", null)), @@ -54,7 +54,7 @@ void shouldListConcepts() { @Test void shouldGetConceptDetails() { CategoricalConcept expected = - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()); + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()); Mockito.when(conceptService.conceptDetail("my_dataset", "/foo//bar")) .thenReturn(Optional.of(expected)); @@ -77,11 +77,11 @@ void shouldNotGetConceptDetails() { @Test void shouldGetConceptTree() { Concept fooBar = - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()); + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()); Concept fooBaz = - new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", 0, 100, Map.of()); + new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", "foo!", 0, 100, Map.of()); CategoricalConcept foo = - new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", List.of(), List.of(fooBar, fooBaz), Map.of()); + new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", "foo!", List.of(), List.of(fooBar, fooBaz), Map.of()); Mockito.when(conceptService.conceptTree("my_dataset", "/foo", 1)) .thenReturn(Optional.of(foo)); @@ -95,11 +95,11 @@ void shouldGetConceptTree() { @Test void shouldGetNotConceptTreeForLargeDepth() { Concept fooBar = - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()); + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()); Concept fooBaz = - new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", 0, 100, Map.of()); + new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", "foo!", 0, 100, Map.of()); CategoricalConcept foo = - new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", List.of(), List.of(fooBar, fooBaz), Map.of()); + new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", "foo!", List.of(), List.of(fooBar, fooBaz), Map.of()); Mockito.when(conceptService.conceptTree("my_dataset", "/foo", 1)) .thenReturn(Optional.of(foo)); @@ -113,11 +113,11 @@ void shouldGetNotConceptTreeForLargeDepth() { @Test void shouldGetNotConceptTreeForNegativeDepth() { Concept fooBar = - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()); + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()); Concept fooBaz = - new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", 0, 100, Map.of()); + new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", "foo!", 0, 100, Map.of()); CategoricalConcept foo = - new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", List.of(), List.of(fooBar, fooBaz), Map.of()); + new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", "foo!", List.of(), List.of(fooBar, fooBaz), Map.of()); Mockito.when(conceptService.conceptTree("my_dataset", "/foo", -1)) .thenReturn(Optional.of(foo)); @@ -130,11 +130,11 @@ void shouldGetNotConceptTreeForNegativeDepth() { @Test void shouldNotGetConceptTreeWhenConceptDNE() { Concept fooBar = - new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", List.of("a", "b"), List.of(), Map.of()); + new CategoricalConcept("/foo//bar", "bar", "Bar", "my_dataset", "foo!", List.of("a", "b"), List.of(), Map.of()); Concept fooBaz = - new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", 0, 100, Map.of()); + new ContinuousConcept("/foo//baz", "baz", "Baz", "my_dataset", "foo!", 0, 100, Map.of()); CategoricalConcept foo = - new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", List.of(), List.of(fooBar, fooBaz), Map.of()); + new CategoricalConcept("/foo", "foo", "Foo", "my_dataset", "foo!", List.of(), List.of(fooBar, fooBaz), Map.of()); Mockito.when(conceptService.conceptTree("my_dataset", "/foo", 1)) .thenReturn(Optional.of(foo)); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java index ba46693..7ea3b5a 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptRepositoryTest.java @@ -48,21 +48,21 @@ static void mySQLProperties(DynamicPropertyRegistry registry) { void shouldListAllConcepts() { List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.unpaged()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", List.of("0", "1"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", List.of("X", "Z"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", List.of("X", "Y"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("foo", "bar"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", List.of("foo", "bar", "baz"), null, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", 0, 0, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", 0, 0, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\", "b", "B", "invalid.invalid", List.of("0", "2"), null, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", List.of("X", "Y", "Z"), null, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\", "2", "2", "invalid.invalid", List.of("Y", "Z"), null, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("bar", "baz"), null, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", List.of("bar", "baz", "qux"), null, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", List.of("foo", "bar", "baz", "qux"), null, null), - new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", 0, 0, null), - new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", 0, 0, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", null, List.of("0", "1"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", null, List.of("X", "Z"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", null, List.of("X", "Y"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("foo", "bar"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", null, List.of("foo", "bar", "baz"), null, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, 0, 0, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", null, 0, 0, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\", "b", "B", "invalid.invalid", null, List.of("0", "2"), null, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", null, List.of("X", "Y", "Z"), null, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\", "2", "2", "invalid.invalid", null, List.of("Y", "Z"), null, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("bar", "baz"), null, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", null, List.of("bar", "baz", "qux"), null, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", null, List.of("foo", "bar", "baz", "qux"), null, null), + new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", null, 0, 0, null), + new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", null, 0, 0, null) ); Assertions.assertEquals(expected, actual); @@ -72,8 +72,8 @@ void shouldListAllConcepts() { void shouldListFirstTwoConcepts() { List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.ofSize(2).first()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", List.of("0", "1"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", List.of("X", "Z"), null, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", null, List.of("0", "1"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", null, List.of("X", "Z"), null, null) ); Assertions.assertEquals(expected, actual); @@ -83,8 +83,8 @@ void shouldListFirstTwoConcepts() { void shouldListNextTwoConcepts() { List actual = subject.getConcepts(new Filter(List.of(), ""), Pageable.ofSize(2).first().next()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", List.of("X", "Y"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("foo", "bar"), null, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", null, List.of("X", "Y"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("foo", "bar"), null, null) ); Assertions.assertEquals(expected, actual); @@ -95,13 +95,13 @@ void shouldFilterConceptsByFacet() { List actual = subject.getConcepts(new Filter(List.of(new Facet("bch", "", "", 1, null, "site", null)), ""), Pageable.unpaged()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", List.of("0", "1"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", List.of("X", "Z"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", List.of("X", "Y"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("foo", "bar"), null, null), - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", List.of("foo", "bar", "baz"), null, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", 0, 0, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", 0, 0, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\", "a", "A", "invalid.invalid", null, List.of("0", "1"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\", "1", "1", "invalid.invalid", null, List.of("X", "Z"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\", "0", "0", "invalid.invalid", null, List.of("X", "Y"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("foo", "bar"), null, null), + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\Y\\\\\\\\", "y", "Y", "invalid.invalid", null, List.of("foo", "bar", "baz"), null, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, 0, 0, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", null, 0, 0, null) ); Assertions.assertEquals(expected, actual); @@ -111,9 +111,9 @@ void shouldFilterConceptsByFacet() { void shouldFilterBySearch() { List actual = subject.getConcepts(new Filter(List.of(), "X"), Pageable.unpaged()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("foo", "bar"), null, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", 0, 0, null), - new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("bar", "baz"), null, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("foo", "bar"), null, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, 0, 0, null), + new CategoricalConcept("\\\\\\\\B\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("bar", "baz"), null, null) ); Assertions.assertEquals(expected, actual); @@ -124,8 +124,8 @@ void shouldFilterByBothSearchAndFacet() { List actual = subject.getConcepts(new Filter(List.of(new Facet("bch", "", "", 1, null, "site", null)), "X"), Pageable.unpaged()); List expected = List.of( - new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", List.of("foo", "bar"), null, null), - new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", 0, 0, null) + new CategoricalConcept("\\\\\\\\A\\\\\\\\0\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, List.of("foo", "bar"), null, null), + new ContinuousConcept("\\\\\\\\A\\\\\\\\1\\\\\\\\X\\\\\\\\", "x", "X", "invalid.invalid", null, 0, 0, null) ); Assertions.assertEquals(expected, actual); @@ -147,7 +147,7 @@ void shouldGetCountWithFilter() { @Test void shouldGetDetailForConcept() { ContinuousConcept expected = - new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", 0, 0, null); + new ContinuousConcept("\\\\\\\\B\\\\\\\\2\\\\\\\\Z\\\\\\\\", "z", "Z", "invalid.invalid", null, 0, 0, null); Optional actual = subject.getConcept(expected.dataset(), expected.conceptPath()); Assertions.assertEquals(Optional.of(expected), actual); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java index 22113cc..f47920f 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java @@ -29,7 +29,7 @@ class ConceptServiceTest { @Test void shouldListConcepts() { List expected = List.of( - new CategoricalConcept("A", "a", "A", "invalid.invalid", List.of(), null, null) + new CategoricalConcept("A", "a", "A", "invalid.invalid", null, List.of(), null, null) ); Filter filter = new Filter(List.of(), ""); Pageable page = Pageable.ofSize(10).first(); @@ -54,7 +54,7 @@ void shouldCountConcepts() { @Test void shouldShowDetailForContinuous() { - ContinuousConcept concept = new ContinuousConcept("path", "", "", "dataset", 0, 1, null); + ContinuousConcept concept = new ContinuousConcept("path", "", "", "dataset", null, 0, 1, null); Map meta = Map.of("MIN", "0", "MAX", "1", "stigmatizing", "true"); Mockito.when(repository.getConcept("dataset", "path")) .thenReturn(Optional.of(concept)); @@ -69,7 +69,7 @@ void shouldShowDetailForContinuous() { @Test void shouldShowDetailForCategorical() { - CategoricalConcept concept = new CategoricalConcept("path", "", "", "dataset", List.of("a"), List.of(), null); + CategoricalConcept concept = new CategoricalConcept("path", "", "", "dataset", null, List.of("a"), List.of(), null); Map meta = Map.of("VALUES", "a", "stigmatizing", "true"); Mockito.when(repository.getConcept("dataset", "path")) .thenReturn(Optional.of(concept)); diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ConceptTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ConceptTest.java index 45ac68e..c48b501 100644 --- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ConceptTest.java +++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/model/ConceptTest.java @@ -14,7 +14,7 @@ class ConceptTest { @Test void shouldRoundTrip() throws JsonProcessingException { - Concept expected = new CategoricalConcept("/foo//bar", "bar", "Bar", "study_a", List.of("a", "b"), List.of(), Map.of()); + Concept expected = new CategoricalConcept("/foo//bar", "bar", "Bar", "study_a", null, List.of("a", "b"), List.of(), Map.of()); String json = objectMapper.writeValueAsString(expected); Concept actual = objectMapper.readValue(json, Concept.class); @@ -37,7 +37,7 @@ void shouldReadCategorical() throws JsonProcessingException { } """; - CategoricalConcept expected = new CategoricalConcept("/foo//bar", "bar", "Bar", "study_a", List.of("a", "b"), null, Map.of()); + CategoricalConcept expected = new CategoricalConcept("/foo//bar", "bar", "Bar", "study_a", null, List.of("a", "b"), null, Map.of()); Concept actual = new ObjectMapper().readValue(json, Concept.class); Assertions.assertEquals(expected, actual); @@ -60,10 +60,23 @@ void shouldReadContinuous() throws JsonProcessingException { } """; - ContinuousConcept expected = new ContinuousConcept("/foo//baz", "baz", "Baz", "study_a", 0, 1, Map.of()); + ContinuousConcept expected = new ContinuousConcept("/foo//baz", "baz", "Baz", "study_a", null, 0, 1, Map.of()); Concept actual = new ObjectMapper().readValue(json, Concept.class); Assertions.assertEquals(expected, actual); Assertions.assertEquals(ConceptType.Continuous, actual.type()); } + + @Test + void shouldIncludeTypeInList() throws JsonProcessingException { + List concepts = List.of( + new ContinuousConcept("/foo//baz", "baz", "Baz", "study_a", null, 0, 1, Map.of()), + new CategoricalConcept("/foo//bar", "bar", "Bar", "study_a", null, List.of("a", "b"), null, Map.of()) + ); + + String actual = new ObjectMapper().writeValueAsString(concepts); + String expected = ""; + + Assertions.assertEquals(expected, actual); + } } \ No newline at end of file