diff --git a/docs/elasticsearch8-upgrade.md b/docs/elasticsearch8-upgrade.md index b83bda791..fcb7aca3b 100644 --- a/docs/elasticsearch8-upgrade.md +++ b/docs/elasticsearch8-upgrade.md @@ -31,7 +31,7 @@ POST codesystem-version/_update_by_query } ``` -### Option 2 - Reindex codesystem-version to change the field type of importDate to long +### Option 2 - Reindex codesystem-version and branch-review to change the field type of importDate and lastUpdated to long #### In Kibana create a new index for codesystem-version-tmp with updated mapping ``` @@ -135,6 +135,137 @@ POST _reindex ``` DELETE codesystem-version-tmp ``` + +#### Create a new index for branch-review-tmp with updated mapping +``` +PUT branch-review-tmp +{ + "mappings":{ + "properties":{ + "_class":{ + "type":"text", + "fields":{ + "keyword":{ + "type":"keyword", + "ignore_above":256 + } + } + }, + "changedConcepts":{ + "type":"long" + }, + "id":{ + "type":"keyword" + }, + "lastUpdated":{ + "type":"long" + }, + "source":{ + "type":"nested", + "properties":{ + "baseTimestamp":{ + "type":"long" + }, + "headTimestamp":{ + "type":"long" + }, + "path":{ + "type":"text", + "fields":{ + "keyword":{ + "type":"keyword", + "ignore_above":256 + } + } + } + } + }, + "sourceIsParent":{ + "type":"boolean" + }, + "status":{ + "type":"keyword" + }, + "target":{ + "type":"nested", + "properties":{ + "baseTimestamp":{ + "type":"long" + }, + "headTimestamp":{ + "type":"long" + }, + "path":{ + "type":"text", + "fields":{ + "keyword":{ + "type":"keyword", + "ignore_above":256 + } + } + } + } + } + } + } +} +``` + +#### Reindex branch-review to branch-review-tmp +``` +POST _reindex +{ + "source": { + "index": "branch-review" + }, + "dest": { + "index": "branch-review-tmp" + }, + "script": { + "source": """ + if (ctx._source.containsKey('lastUpdated')) { + def value = ctx._source['lastUpdated']; + // Try parsing the value as a date and convert to millis + try { + if (value instanceof String) { + // Convert date to milliseconds + ZonedDateTime zdt = ZonedDateTime.parse(value); + long milliSinceEpoch = zdt.toInstant().toEpochMilli(); + ctx._source.lastUpdated = milliSinceEpoch; + } + } catch (Exception e) { + // If parsing fails, handle the failure (e.g., log an error, set a default value) + ctx._source.lastUpdated = 1000; // Set a default value for debug, adjust as needed + } + } + """, + "lang": "painless" + } +} +``` + +#### Delete branch-review +``` +DELETE branch-review +``` + +#### Reindex branch-review-tmp back to the original index name. +``` +POST _reindex +{ + "source": { + "index": "branch-review-tmp" + }, + "dest": { + "index": "branch-review" + } +} +``` + +#### Delete branch-review-tmp +``` +DELETE branch-review-tmp +``` Note: You can use curl for above operations if you don't have Kibana installed. See more details on [Reindex API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html) ## Step two - Upgrade cluster to Elasticsearch 8 diff --git a/pom.xml b/pom.xml index 230d432be..201d2ca34 100644 --- a/pom.xml +++ b/pom.xml @@ -6,15 +6,17 @@ SNOMED CT Terminology Server Using Elasticsearch snowstorm - 10.6.1 + 10.7.0-SNAPSHOT org.snomed snomed-parent-bom - 3.7.1 + 3.9.0-SNAPSHOT ${project.artifactId} + + 4.9.0 + + org.apache.jena + jena-core + ${jena_version} + + + org.apache.jena + jena-arq + ${jena_version} + + io.kaicode diff --git a/src/main/java/org/snomed/snowstorm/core/data/domain/Concepts.java b/src/main/java/org/snomed/snowstorm/core/data/domain/Concepts.java index 5fdb1fda1..7428c4e68 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/domain/Concepts.java +++ b/src/main/java/org/snomed/snowstorm/core/data/domain/Concepts.java @@ -14,6 +14,7 @@ public class Concepts { public static final String CORE_MODULE = "900000000000207008"; public static final String MODEL_MODULE = "900000000000012004"; public static final String ICD10_MODULE = "449080006"; + public static final String ICD11_MODULE = "1204363008"; public static final String COMMON_FRENCH_MODULE = "11000241103"; public static final String MODULE = "900000000000443000"; diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/CodeSystemUpgradeService.java b/src/main/java/org/snomed/snowstorm/core/data/services/CodeSystemUpgradeService.java index c1d2bf6a3..c07632030 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/CodeSystemUpgradeService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/CodeSystemUpgradeService.java @@ -4,6 +4,7 @@ import io.kaicode.elasticvc.api.PathUtil; import io.kaicode.elasticvc.domain.Branch; import io.kaicode.elasticvc.domain.Metadata; +import org.apache.activemq.command.ActiveMQTopic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snomed.snowstorm.core.data.domain.CodeSystem; @@ -14,6 +15,7 @@ import org.snomed.snowstorm.dailybuild.DailyBuildService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.jms.core.JmsTemplate; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -24,7 +26,8 @@ import java.util.concurrent.ExecutorService; import static org.snomed.snowstorm.core.data.services.BranchMetadataHelper.INTERNAL_METADATA_KEY; -import static org.snomed.snowstorm.core.data.services.BranchMetadataKeys.*; +import static org.snomed.snowstorm.core.data.services.BranchMetadataKeys.DEPENDENCY_PACKAGE; +import static org.snomed.snowstorm.core.data.services.BranchMetadataKeys.DEPENDENCY_RELEASE; @Service public class CodeSystemUpgradeService { @@ -55,9 +58,18 @@ public class CodeSystemUpgradeService { @Autowired private ExecutorService executorService; + @Autowired + private JmsTemplate jmsTemplate; + @Value("${snowstorm.rest-api.readonly}") private boolean isReadOnly; + @Value("${snowstorm.codesystem-version.message.enabled}") + private boolean jmsMessageEnabled; + + @Value("${jms.queue.prefix}") + private String jmsQueuePrefix; + private static final Map upgradeJobMap = new HashMap<>(); private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -202,6 +214,19 @@ public synchronized void upgrade(String id, CodeSystem codeSystem, Integer newDe if (job != null) { job.setStatus(CodeSystemUpgradeJob.UpgradeStatus.COMPLETED); } + + if (jmsMessageEnabled) { + Map payload = new HashMap<>(); + payload.put("codeSystemShortName", codeSystem.getShortName()); + payload.put("codeSystemBranchPath", codeSystem.getBranchPath()); + payload.put(DEPENDENCY_PACKAGE, newParentVersion.getReleasePackage()); + payload.put(DEPENDENCY_RELEASE, String.valueOf(newParentVersion.getEffectiveDate())); + + String topicDestination = jmsQueuePrefix + ".upgrade.complete"; + logger.info("Sending JMS Topic - destination {}, payload {}...", topicDestination, payload); + jmsTemplate.convertAndSend(new ActiveMQTopic(topicDestination), payload); + } + upgradedSuccessfully = true; } catch (Exception e) { logger.error("Upgrade on {} failed", branchPath, e); diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java b/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java index e1920ed10..3deb999bd 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java @@ -870,6 +870,17 @@ public List copyConcepts(String ecl, String sourceBranchPath, Strin final Branch sourceBranch = branchService.findBranchOrThrow(sourceBranchPath, true); final Branch destinationBranch = branchService.findBranchOrThrow(destinationBranchPath, true); + CodeSystem codeSystem = codeSystemService.findClosestCodeSystemUsingAnyBranch(destinationBranchPath, false); + if (codeSystem != null) { + List codeSystemVersions = codeSystemService.findAllVersions(codeSystem.getShortName(), true, true); + String branchPath = destinationBranch.getPath(); + for (CodeSystemVersion codeSystemVersion : codeSystemVersions) { + if (Objects.equals(branchPath, codeSystemVersion.getBranchPath())) { + throw new ServiceException("Cannot donate concepts from " + sourceBranchPath + " to versioned " + destinationBranchPath); + } + } + } + if (getDefaultModuleId(sourceBranch).equals(getDefaultModuleId(destinationBranch))) { throw new ServiceException("Cannot donate concepts from " + sourceBranchPath + " to " + destinationBranchPath + " as they are from the same module: " + getDefaultModuleId(sourceBranch)); } diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/DescriptionService.java b/src/main/java/org/snomed/snowstorm/core/data/services/DescriptionService.java index 289ddde6a..44474ec7f 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/DescriptionService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/DescriptionService.java @@ -943,7 +943,7 @@ private void addClause(Query queryClause, BoolQuery.Builder boolBuilder, boolean } } - private List analyze(String text, StandardAnalyzer analyzer) { + public static List analyze(String text, StandardAnalyzer analyzer) { List result = new ArrayList<>(); try { TokenStream tokenStream = analyzer.tokenStream("contents", text); @@ -953,12 +953,13 @@ private List analyze(String text, StandardAnalyzer analyzer) { result.add(attr.toString()); } } catch (IOException e) { - logger.error("Failed to analyze text {}", text, e); + LoggerFactory.getLogger(DescriptionService.class) + .error("Failed to analyze text {}", text, e); } return result; } - private String constructSimpleQueryString(String searchTerm) { + public static String constructSimpleQueryString(String searchTerm) { return (searchTerm.trim().replace(" ", "* ") + "*").replace("**", "*"); } @@ -1000,7 +1001,7 @@ private String constructRegexQuery(String term) { return regexBuilder.toString(); } - private String constructSearchTerm(List tokens) { + public static String constructSearchTerm(List tokens) { StringBuilder builder = new StringBuilder(); for (String token : tokens) { builder.append(token); diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/ModuleDependencyService.java b/src/main/java/org/snomed/snowstorm/core/data/services/ModuleDependencyService.java index 3263e4c19..c6e6140c8 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/ModuleDependencyService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/ModuleDependencyService.java @@ -42,7 +42,7 @@ public class ModuleDependencyService extends ComponentService { public static final Set CORE_MODULES = Set.of(Concepts.CORE_MODULE, Concepts.MODEL_MODULE); - public Set SI_MODULES = new HashSet<>(Set.of(Concepts.CORE_MODULE, Concepts.MODEL_MODULE, Concepts.ICD10_MODULE)); + public Set SI_MODULES = new HashSet<>(Set.of(Concepts.CORE_MODULE, Concepts.MODEL_MODULE, Concepts.ICD10_MODULE, Concepts.ICD11_MODULE)); @Autowired private BranchService branchService; @@ -94,10 +94,13 @@ public synchronized void refreshCache() { cacheValidAt = currentTime; logger.info("MDR cache of International Modules refreshed for HEAD time: {}", currentTime); - //During unit tests, or in non-standard installations we might not see the ICD-10 Module + //During unit tests, or in non-standard installations we might not see the ICD-10 and ICD-11 Modules if (!cachedInternationalModules.contains(Concepts.ICD10_MODULE)) { SI_MODULES.remove(Concepts.ICD10_MODULE); } + if (!cachedInternationalModules.contains(Concepts.ICD11_MODULE)) { + SI_MODULES.remove(Concepts.ICD11_MODULE); + } derivativeModules = cachedInternationalModules.stream() .filter(m -> !SI_MODULES.contains(m)) diff --git a/src/main/java/org/snomed/snowstorm/fhir/services/FHIRConceptService.java b/src/main/java/org/snomed/snowstorm/fhir/services/FHIRConceptService.java index 87477e4c7..82943b575 100644 --- a/src/main/java/org/snomed/snowstorm/fhir/services/FHIRConceptService.java +++ b/src/main/java/org/snomed/snowstorm/fhir/services/FHIRConceptService.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.entity.TermConcept; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; import com.google.common.collect.Iterables; - import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeType; import org.slf4j.Logger; @@ -19,10 +18,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.client.elc.NativeQuery; import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.stereotype.Service; import java.util.*; @@ -182,6 +182,7 @@ public FHIRConcept findConcept(FHIRCodeSystemVersion systemVersion, String code) public Page findConcepts(BoolQuery.Builder fhirConceptQuery, PageRequest pageRequest) { NativeQuery searchQuery = new NativeQueryBuilder() .withQuery(fhirConceptQuery.build()._toQuery()) + .withSort(Sort.by(FHIRConcept.Fields.DISPLAY_LENGTH, FHIRConcept.Fields.CODE)) .withPageable(pageRequest) .build(); searchQuery.setTrackTotalHits(true); @@ -189,9 +190,10 @@ public Page findConcepts(BoolQuery.Builder fhirConceptQuery, PageRe return toPage(elasticsearchOperations.search(searchQuery, FHIRConcept.class), pageRequest); } - public SearchAfterPage findConceptCodes(BoolQuery.Builder fhirConceptQuery, PageRequest pageRequest) { + public SearchAfterPage findConceptCodes(BoolQuery fhirConceptQuery, PageRequest pageRequest) { NativeQuery searchQuery = new NativeQueryBuilder() - .withQuery(fhirConceptQuery.build()._toQuery()) + .withQuery(fhirConceptQuery._toQuery()) + .withSort(Sort.by(FHIRConcept.Fields.CODE)) .withPageable(pageRequest) .build(); searchQuery.setTrackTotalHits(true); diff --git a/src/main/java/org/snomed/snowstorm/fhir/services/FHIRValueSetService.java b/src/main/java/org/snomed/snowstorm/fhir/services/FHIRValueSetService.java index 4d04b98a1..76c4e875d 100644 --- a/src/main/java/org/snomed/snowstorm/fhir/services/FHIRValueSetService.java +++ b/src/main/java/org/snomed/snowstorm/fhir/services/FHIRValueSetService.java @@ -1,10 +1,10 @@ package org.snomed.snowstorm.fhir.services; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; -import co.elastic.clients.elasticsearch._types.query_dsl.PrefixQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Operator; import com.google.common.base.Strings; import it.unimi.dsi.fastutil.longs.LongArrayList; - +import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.hl7.fhir.r4.model.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,6 +15,7 @@ import org.snomed.snowstorm.core.data.domain.QueryConcept; import org.snomed.snowstorm.core.data.domain.ReferenceSetMember; import org.snomed.snowstorm.core.data.services.ConceptService; +import org.snomed.snowstorm.core.data.services.DescriptionService; import org.snomed.snowstorm.core.data.services.QueryService; import org.snomed.snowstorm.core.data.services.ReferenceSetMemberService; import org.snomed.snowstorm.core.data.services.pojo.MemberSearchRequest; @@ -30,10 +31,11 @@ import org.snomed.snowstorm.rest.pojo.SearchAfterPageRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.*; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.client.elc.NativeQuery; import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder; +import org.springframework.data.elasticsearch.client.elc.Queries; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.stereotype.Service; import java.net.URLDecoder; @@ -41,11 +43,12 @@ import java.util.*; import java.util.stream.Collectors; +import static co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.bool; import static io.kaicode.elasticvc.api.ComponentService.LARGE_PAGE; +import static io.kaicode.elasticvc.helper.QueryHelper.termQuery; +import static io.kaicode.elasticvc.helper.QueryHelper.termsQuery; import static java.lang.Boolean.TRUE; import static java.lang.String.format; -import static co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.*; -import static io.kaicode.elasticvc.helper.QueryHelper.*; import static org.snomed.snowstorm.core.data.services.ReferenceSetMemberService.AGGREGATION_MEMBER_COUNTS_BY_REFERENCE_SET; import static org.snomed.snowstorm.core.util.CollectionUtils.orEmpty; import static org.snomed.snowstorm.fhir.services.FHIRHelper.*; @@ -282,7 +285,7 @@ public ValueSet expand(final ValueSetExpansionParameters params, String displayL // FHIR Concept Expansion (non-SNOMED) String sortField = filter != null ? "displayLen" : "code"; pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageRequest.getPageSize(), Sort.Direction.ASC, sortField); - BoolQuery.Builder fhirConceptQuery = getFhirConceptQuery(codeSelectionCriteria, filter); + BoolQuery fhirConceptQuery = getFhirConceptQuery(codeSelectionCriteria, filter).build(); int offsetRequested = (int) pageRequest.getOffset(); int limitRequested = (int) (pageRequest.getOffset() + pageRequest.getPageSize()); @@ -317,14 +320,16 @@ public ValueSet expand(final ValueSetExpansionParameters params, String displayL conceptsToLoad = new ArrayList<>(); } if (!conceptsToLoad.isEmpty()) { - fhirConceptQuery.must(termsQuery(FHIRConcept.Fields.CODE, conceptsToLoad)); - conceptsPage = conceptService.findConcepts(fhirConceptQuery, LARGE_PAGE); + BoolQuery.Builder conceptsToLoadQuery = bool() + .must(fhirConceptQuery._toQuery()) + .must(termsQuery(FHIRConcept.Fields.CODE, conceptsToLoad)); + conceptsPage = conceptService.findConcepts(conceptsToLoadQuery, LARGE_PAGE); conceptsPage = new PageImpl<>(conceptsPage.getContent(), pageRequest, totalResults); } else { conceptsPage = new PageImpl<>(new ArrayList<>(), pageRequest, totalResults); } } else { - conceptsPage = conceptService.findConcepts(fhirConceptQuery, pageRequest); + conceptsPage = conceptService.findConcepts(bool().must(fhirConceptQuery._toQuery()), pageRequest); } } @@ -390,7 +395,10 @@ private BoolQuery.Builder getFhirConceptQuery(CodeSelectionCriteria codeSelectio BoolQuery.Builder masterQuery = bool(); masterQuery.must(contentQuery.build()._toQuery()); if (termFilter != null) { - masterQuery.must(PrefixQuery.of(pq -> pq.field(FHIRConcept.Fields.DISPLAY).value(termFilter.toLowerCase()))._toQuery()); + List elasticAnalyzedWords = DescriptionService.analyze(termFilter, new StandardAnalyzer()); + String searchTerm = DescriptionService.constructSearchTerm(elasticAnalyzedWords); + String query = DescriptionService.constructSimpleQueryString(searchTerm); + masterQuery.filter(Queries.queryStringQuery(FHIRConcept.Fields.DISPLAY, query, Operator.And, 2.0f)._toQuery()); } return masterQuery; } diff --git a/src/test/java/org/snomed/snowstorm/core/data/services/ConceptServiceTest.java b/src/test/java/org/snomed/snowstorm/core/data/services/ConceptServiceTest.java index 60466e974..65252d7b7 100644 --- a/src/test/java/org/snomed/snowstorm/core/data/services/ConceptServiceTest.java +++ b/src/test/java/org/snomed/snowstorm/core/data/services/ConceptServiceTest.java @@ -17,6 +17,8 @@ import org.snomed.snowstorm.AbstractTest; import org.snomed.snowstorm.config.Config; import org.snomed.snowstorm.core.data.domain.*; +import org.snomed.snowstorm.core.data.domain.review.MergeReview; +import org.snomed.snowstorm.core.data.domain.review.MergeReviewConceptVersions; import org.snomed.snowstorm.core.data.services.pojo.AsyncConceptChangeBatch; import org.snomed.snowstorm.core.data.services.pojo.DescriptionCriteria; import org.snomed.snowstorm.core.data.services.pojo.MemberSearchRequest; @@ -3011,6 +3013,66 @@ void testActivatingInvalidAxiomThrowsException() throws ServiceException { }); } + @Test + void copyConcepts_ShouldThrowException_WhenTargetIsVersionedBranch() throws ServiceException { + String intMain = "MAIN"; + String extMain = "MAIN/SNOMEDCT-XX"; + Map intPreferred = Map.of(US_EN_LANG_REFSET, descriptionAcceptabilityNames.get(PREFERRED), GB_EN_LANG_REFSET, descriptionAcceptabilityNames.get(PREFERRED)); + Map intAcceptable = Map.of(US_EN_LANG_REFSET, descriptionAcceptabilityNames.get(PREFERRED), GB_EN_LANG_REFSET, descriptionAcceptabilityNames.get(ACCEPTABLE)); + String ci = "CASE_INSENSITIVE"; + Concept concept; + Description description; + CodeSystem codeSystem; + + // Create International Concept + concept = new Concept() + .addDescription(new Description("Medicine (medicine)").setTypeId(FSN).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addDescription(new Description("Medicine").setTypeId(SYNONYM).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addAxiom(new Relationship(ISA, SNOMEDCT_ROOT)) + .addRelationship(new Relationship(ISA, SNOMEDCT_ROOT)); + concept = conceptService.create(concept, intMain); + String medicineId = concept.getConceptId(); + + // Version International + codeSystem = codeSystemService.find("SNOMEDCT"); + codeSystemService.createVersion(codeSystem, 20240101, "20240101"); + + // Create Extension + codeSystem = codeSystemService.createCodeSystem(new CodeSystem("SNOMEDCT-XX", extMain)); + concept = conceptService.create( + new Concept() + .addDescription(new Description("Extension module (module)").setTypeId(FSN).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addDescription(new Description("Extension module").setTypeId(SYNONYM).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addAxiom(new Relationship(ISA, MODULE)), + extMain + ); + String extModuleA = concept.getConceptId(); + branchService.updateMetadata(extMain, Map.of(Config.DEFAULT_MODULE_ID_KEY, extModuleA, Config.EXPECTED_EXTENSION_MODULES, List.of(extModuleA))); + + // Create Extension concept + concept = new Concept() + .setModuleId(extModuleA) + .addDescription(new Description("Paracetamol (medicine)").setTypeId(FSN).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addDescription(new Description("Paracetamol").setTypeId(SYNONYM).setCaseSignificance(ci).setAcceptabilityMap(intPreferred)) + .addAxiom(new Relationship(ISA, medicineId)) + .addRelationship(new Relationship(ISA, medicineId)); + concept = conceptService.create(concept, extMain); + String paracetamolId = concept.getConceptId(); + + // Version Extension + codeSystem = codeSystemService.find("SNOMEDCT-XX"); + codeSystemService.createVersion(codeSystem, 20240102, "20240102"); + + // Copy Extension to International + assertThrows(ServiceException.class, () -> { + conceptService.copyConcepts("<< " + paracetamolId, extMain, "MAIN/2024-01-01", true); + }); + + // Assert copying failed + concept = conceptService.find(paracetamolId, "MAIN/2024-01-01"); + assertNull(concept); + } + private Description getDescriptionByTerm(Concept concept, String term) { if (concept == null) { return null; diff --git a/src/test/java/org/snomed/snowstorm/fhir/services/FHIRValueSetProviderExpandGenericTest.java b/src/test/java/org/snomed/snowstorm/fhir/services/FHIRValueSetProviderExpandGenericTest.java index 9d7812e81..7550efa01 100644 --- a/src/test/java/org/snomed/snowstorm/fhir/services/FHIRValueSetProviderExpandGenericTest.java +++ b/src/test/java/org/snomed/snowstorm/fhir/services/FHIRValueSetProviderExpandGenericTest.java @@ -17,9 +17,9 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class FHIRValueSetProviderExpandGenericTest extends AbstractFHIRTest { @@ -196,4 +196,54 @@ public void testExpandIncludesOtherValueSet() { assertEquals(2, valueSet.getExpansion().getContains().size()); } + @Test + void testSearchMultipleWordsOrPrefixes() { + // "display": "additive, propagating", + System.out.println("----------"); + ResponseEntity response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs", HttpMethod.GET, null, String.class); + System.out.println("Search no filter"); + assertExpand(response, 8, null); + + System.out.println("----------"); + response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=additive", HttpMethod.GET, null, String.class); + System.out.println("Search additive"); + System.out.println(response.getBody()); + assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']"); + + System.out.println("----------"); + response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=additive propagating", HttpMethod.GET, null, String.class); + System.out.println("Search additive propagating"); + System.out.println(response.getBody()); + assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']"); + + System.out.println("----------"); + response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=add", HttpMethod.GET, null, String.class); + System.out.println("Search add"); + System.out.println(response.getBody()); + assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']"); + + System.out.println("----------"); + response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=add prop non", HttpMethod.GET, null, String.class); + System.out.println("Search add prop non"); + System.out.println(response.getBody()); + assertExpand(response, 1, "[AN|'additive, non-propagating']"); + + System.out.println("----------"); + } + + private void assertExpand(ResponseEntity response, int expected, String expectedCodingString) { + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + ValueSet valueSet = fhirJsonParser.parseResource(ValueSet.class, response.getBody()); + List contains = valueSet.getExpansion().getContains(); + assertEquals(expected, contains.size()); + if (expectedCodingString != null) { + assertEquals(expectedCodingString, toCodingsString(contains)); + } + } + + private String toCodingsString(List contains) { + return contains.stream().map(coding -> "%s|'%s'".formatted(coding.getCode(), coding.getDisplay())).toList().toString(); + } + }