From e6c3857ebf81aea1d3e97ecef0b34dd9050a5c5d Mon Sep 17 00:00:00 2001 From: Hernaldo Urbina Date: Tue, 26 Mar 2024 12:35:33 -0600 Subject: [PATCH 01/19] ATL-17: Updating branch with latest release --- .../ohdsi/webapi/conceptset/ConceptSet.java | 12 +++++ .../criteria/ConceptSetCriterion.java | 47 +++++++++++++++++++ .../ConceptSetCriterionRepository.java | 6 +++ .../webapi/service/ConceptSetService.java | 16 ++++++- .../webapi/service/dto/ConceptSetDTO.java | 8 ++++ ...0.2023121100_add_search_criteria_table.sql | 11 +++++ 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java create mode 100644 src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java index ee49b11226..662362dfa1 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java @@ -26,8 +26,10 @@ import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; +import javax.persistence.OneToOne; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; +import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterion; import org.ohdsi.webapi.model.CommonEntity; import org.ohdsi.webapi.model.CommonEntityExt; import org.ohdsi.webapi.tag.domain.Tag; @@ -65,6 +67,16 @@ public class ConceptSet extends CommonEntityExt implements Serializable inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")) private Set tags; + @OneToOne(mappedBy = "conceptSet") + private ConceptSetCriterion conceptSetCriterion; + + public void setConceptSetCriterion(ConceptSetCriterion conceptSetCriterion) { + this.conceptSetCriterion = conceptSetCriterion; + } + public ConceptSetCriterion getConceptSetCriterion() { + return conceptSetCriterion; + } + @Override public Integer getId() { return id; diff --git a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java new file mode 100644 index 0000000000..7b5b74f20b --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java @@ -0,0 +1,47 @@ +package org.ohdsi.webapi.conceptset.criteria; + +import org.ohdsi.webapi.conceptset.ConceptSet; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Id; +import javax.persistence.Column; +import javax.persistence.JoinColumn; +import javax.persistence.FetchType; +import javax.persistence.OneToOne; +import javax.persistence.MapsId; + +@Entity +@Table(name = "concept_set_criteria", schema = "webapi") +public class ConceptSetCriterion { + + @Id + @Column(name = "concept_set_id", nullable = false) + private Integer id; + + @MapsId + @OneToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "concept_set_id", nullable = false) + private ConceptSet conceptSet; + + @Column(name = "criteria") + private String criteria; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public ConceptSet getConceptSet() { + return conceptSet; + } + public void setConceptSet(ConceptSet conceptSet) { + this.conceptSet = conceptSet; + } + public String getCriteria() { + return criteria; + } + public void setCriteria(String criteria) { + this.criteria = criteria; + } +} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java new file mode 100644 index 0000000000..11076f1731 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java @@ -0,0 +1,6 @@ +package org.ohdsi.webapi.conceptset.criteria; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ConceptSetCriterionRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 2f80ef991e..ec105f4f52 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -35,6 +35,8 @@ import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfo; import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfoRepository; import org.ohdsi.webapi.conceptset.ConceptSetItem; +import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterion; +import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterionRepository; import org.ohdsi.webapi.conceptset.dto.ConceptSetVersionFullDTO; import org.ohdsi.webapi.exception.ConceptNotExistException; import org.ohdsi.webapi.security.PermissionService; @@ -74,6 +76,8 @@ @Transactional @Path("/conceptset/") public class ConceptSetService extends AbstractDaoService implements HasTags { + @Autowired + private ConceptSetCriterionRepository criteriaRepository; @Autowired private ConceptSetGenerationInfoRepository conceptSetGenerationInfoRepository; @@ -461,8 +465,15 @@ public ConceptSetDTO createConceptSet(ConceptSetDTO conceptSetDTO) { updated.setCreatedBy(user); updated.setCreatedDate(new Date()); updated.setTags(null); - updateConceptSet(updated, conceptSet); - return conversionService.convert(updated, ConceptSetDTO.class); + + // save criteria + ConceptSetCriterion criteria = new ConceptSetCriterion(); + criteria.setConceptSet(updated); + criteria.setId(updated.getId()); + criteria.setCriteria(conceptSetDTO.getCriteria()); + updated.setConceptSetCriterion(criteria); + ConceptSet conceptSetUpdate = updateConceptSet(updated, conceptSet); + return conversionService.convert(conceptSetUpdate, ConceptSetDTO.class); } /** @@ -528,6 +539,7 @@ private ConceptSet updateConceptSet(ConceptSet dst, ConceptSet src) { dst.setDescription(src.getDescription()); dst.setModifiedDate(new Date()); dst.setModifiedBy(user); + dst.setConceptSetCriterion(dst.getConceptSetCriterion()); dst = this.getConceptSetRepository().save(dst); return dst; diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java index e91993332d..5e9d535725 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java @@ -5,6 +5,7 @@ public class ConceptSetDTO extends CommonEntityExtDTO { private Integer id; private String name; private String description; + private String criteria; public Integer getId() { return id; @@ -29,4 +30,11 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + public void setCriteria(String criteria) { + this.criteria = criteria; + } + public String getCriteria() { + return criteria; + } } diff --git a/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql b/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql new file mode 100644 index 0000000000..ad943c5980 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE ${ohdsiSchema}.concept_set_criteria +( + concept_set_id integer NOT NULL, + criteria character varying, + PRIMARY KEY (concept_set_id), + CONSTRAINT "conceptSetFk" FOREIGN KEY (concept_set_id) + REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION + NOT VALID +); From 633eb78b326ef04d9022a191953dd34e4d2ef4d5 Mon Sep 17 00:00:00 2001 From: "hernaldo.urbina" Date: Fri, 3 May 2024 18:05:43 +0700 Subject: [PATCH 02/19] ATL-17: Add feature about the metadata in concept set --- .../ohdsi/webapi/conceptset/ConceptSet.java | 11 -- .../criteria/ConceptSetCriterion.java | 47 ------- .../ConceptSetCriterionRepository.java | 6 - .../metadata/ConceptSetMetaData.java | 76 ++++++++++ .../ConceptSetMetaDataRepository.java | 20 +++ .../ConceptSetMetadataPermissionSchema.java | 30 ++++ .../webapi/security/model/EntityType.java | 4 +- .../webapi/service/AbstractDaoService.java | 7 + .../webapi/service/ConceptSetService.java | 130 +++++++++++++++--- .../webapi/service/dto/ConceptSetDTO.java | 7 - .../service/dto/ConceptSetMetaDataDTO.java | 27 ++++ .../ohdsi/webapi/service/dto/MetaDataDTO.java | 76 ++++++++++ ...conceptset_metadata_module_permissions.sql | 29 ++++ src/main/resources/i18n/messages_en.json | 11 +- src/main/resources/i18n/messages_ko.json | 11 +- src/main/resources/i18n/messages_ru.json | 11 +- src/main/resources/i18n/messages_zh.json | 11 +- 17 files changed, 412 insertions(+), 102 deletions(-) delete mode 100644 src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java delete mode 100644 src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java create mode 100644 src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java create mode 100644 src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java index 662362dfa1..e19f97ebca 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java @@ -29,7 +29,6 @@ import javax.persistence.OneToOne; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; -import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterion; import org.ohdsi.webapi.model.CommonEntity; import org.ohdsi.webapi.model.CommonEntityExt; import org.ohdsi.webapi.tag.domain.Tag; @@ -67,16 +66,6 @@ public class ConceptSet extends CommonEntityExt implements Serializable inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")) private Set tags; - @OneToOne(mappedBy = "conceptSet") - private ConceptSetCriterion conceptSetCriterion; - - public void setConceptSetCriterion(ConceptSetCriterion conceptSetCriterion) { - this.conceptSetCriterion = conceptSetCriterion; - } - public ConceptSetCriterion getConceptSetCriterion() { - return conceptSetCriterion; - } - @Override public Integer getId() { return id; diff --git a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java deleted file mode 100644 index 7b5b74f20b..0000000000 --- a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterion.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.ohdsi.webapi.conceptset.criteria; - -import org.ohdsi.webapi.conceptset.ConceptSet; -import javax.persistence.Entity; -import javax.persistence.Table; -import javax.persistence.Id; -import javax.persistence.Column; -import javax.persistence.JoinColumn; -import javax.persistence.FetchType; -import javax.persistence.OneToOne; -import javax.persistence.MapsId; - -@Entity -@Table(name = "concept_set_criteria", schema = "webapi") -public class ConceptSetCriterion { - - @Id - @Column(name = "concept_set_id", nullable = false) - private Integer id; - - @MapsId - @OneToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "concept_set_id", nullable = false) - private ConceptSet conceptSet; - - @Column(name = "criteria") - private String criteria; - - public Integer getId() { - return id; - } - public void setId(Integer id) { - this.id = id; - } - public ConceptSet getConceptSet() { - return conceptSet; - } - public void setConceptSet(ConceptSet conceptSet) { - this.conceptSet = conceptSet; - } - public String getCriteria() { - return criteria; - } - public void setCriteria(String criteria) { - this.criteria = criteria; - } -} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java b/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java deleted file mode 100644 index 11076f1731..0000000000 --- a/src/main/java/org/ohdsi/webapi/conceptset/criteria/ConceptSetCriterionRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.ohdsi.webapi.conceptset.criteria; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ConceptSetCriterionRepository extends JpaRepository { - -} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java new file mode 100644 index 0000000000..6b23f032ca --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java @@ -0,0 +1,76 @@ +package org.ohdsi.webapi.conceptset.metadata; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.ohdsi.webapi.model.CommonEntity; + +@Entity(name = "ConceptSetMetaData") +@Table(name = "concept_set_meta_data") +public class ConceptSetMetaData extends CommonEntity implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GenericGenerator( + name = "concept_set_meta_data_generator", + strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", + parameters = { + @Parameter(name = "sequence_name", value = "concept_set_meta_data_sequence"), + @Parameter(name = "increment_size", value = "1") + } + ) + @GeneratedValue(generator = "concept_set_meta_data_generator") + @Column(name="concept_set_meta_data_id") + private Integer id; + + @Column(name = "concept_set_id", nullable = false) + private Integer conceptSetId; + + @Column(name = "concept_id") + private Integer conceptId; + + @Column(name = "metadata") + private String metadata; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getConceptSetId() { + return conceptSetId; + } + + public void setConceptSetId(Integer conceptSetId) { + this.conceptSetId = conceptSetId; + } + + public Integer getConceptId() { + return conceptId; + } + + public void setConceptId(Integer conceptId) { + this.conceptId = conceptId; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } +} diff --git a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java new file mode 100644 index 0000000000..82f6494981 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java @@ -0,0 +1,20 @@ +package org.ohdsi.webapi.conceptset.metadata; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ConceptSetMetaDataRepository extends JpaRepository { + + @Query("DELETE FROM ConceptSetMetaData cc WHERE cc.conceptSetId = :conceptSetId and cc.conceptId in :conceptId") + void deleteMetadataByConceptSetIdAndInConceptId(int conceptSetId, List conceptId); + + void deleteMetadataByConceptSetIdAndConceptId(int conceptSetId, int conceptId); + + List findByConceptSetId(int conceptSetId); + ConceptSetMetaData findById(int id); + void deleteById(int id); + Optional findConceptSetMetaDataByConceptIdAndConceptId(int conceptSetId, int conceptId); +} diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java new file mode 100644 index 0000000000..c3e7605aee --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java @@ -0,0 +1,30 @@ +package org.ohdsi.webapi.security.model; + +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class ConceptSetMetadataPermissionSchema extends EntityPermissionSchema { + + private static Map writePermissions = new HashMap() { + { + put("conceptset:*:metadata:put", "Create Concept Set Metadata"); + put("conceptset:update:*:metadata:put", "Update Concept Set Metadata"); + put("conceptset:%s:metadata:delete", "Delete Concept Set Metadata with ID %s"); + } + }; + + private static Map readPermissions = new HashMap() { + { + put("conceptset:%s:metadata:get", "List Concept Set Metadata with id %s"); + put("conceptset:*:metadata:get", "View Concept Set Metadata expression"); + } + }; + + public ConceptSetMetadataPermissionSchema() { + + super(EntityType.CONCEPT_SET_METADATA, readPermissions, writePermissions); + } +} diff --git a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java index d5a9d63acf..3ef71b0551 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java @@ -4,6 +4,7 @@ import org.ohdsi.webapi.cohortdefinition.CohortDefinition; import org.ohdsi.webapi.cohortsample.CohortSample; import org.ohdsi.webapi.conceptset.ConceptSet; +import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaData; import org.ohdsi.webapi.estimation.Estimation; import org.ohdsi.webapi.feanalysis.domain.FeAnalysisEntity; import org.ohdsi.webapi.ircalc.IncidenceRateAnalysis; @@ -26,7 +27,8 @@ public enum EntityType { PREDICTION(PredictionAnalysis.class), COHORT_SAMPLE(CohortSample.class), TAG(Tag.class), - REUSABLE(Reusable.class); + REUSABLE(Reusable.class), + CONCEPT_SET_METADATA(ConceptSetMetaData .class); private final Class entityClass; diff --git a/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java b/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java index 95277cbe68..12df361557 100644 --- a/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java +++ b/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java @@ -20,6 +20,7 @@ import org.ohdsi.webapi.conceptset.ConceptSetComparison; import org.ohdsi.webapi.conceptset.ConceptSetItemRepository; import org.ohdsi.webapi.conceptset.ConceptSetRepository; +import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaDataRepository; import org.ohdsi.webapi.exception.BadRequestAtlasException; import org.ohdsi.webapi.ircalc.IncidenceRateAnalysis; import org.ohdsi.webapi.model.CommonEntity; @@ -106,6 +107,9 @@ public abstract class AbstractDaoService extends AbstractAdminService { @Autowired private ConceptSetItemRepository conceptSetItemRepository; + @Autowired + private ConceptSetMetaDataRepository conceptSetMetaDataRepository; + @Autowired protected Security security; @@ -120,6 +124,9 @@ public abstract class AbstractDaoService extends AbstractAdminService { public ConceptSetItemRepository getConceptSetItemRepository() { return conceptSetItemRepository; } + public ConceptSetMetaDataRepository getConceptSetMetaDataRepository() { + return conceptSetMetaDataRepository; + } @Autowired private ConceptSetRepository conceptSetRepository; diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index ec105f4f52..4e1a71a70c 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -16,6 +16,7 @@ package org.ohdsi.webapi.service; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -25,6 +26,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.shiro.authz.UnauthorizedException; import org.ohdsi.circe.vocabulary.ConceptSetExpression; import org.ohdsi.vocabulary.Concept; @@ -35,12 +38,13 @@ import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfo; import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfoRepository; import org.ohdsi.webapi.conceptset.ConceptSetItem; -import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterion; -import org.ohdsi.webapi.conceptset.criteria.ConceptSetCriterionRepository; import org.ohdsi.webapi.conceptset.dto.ConceptSetVersionFullDTO; +import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaData; import org.ohdsi.webapi.exception.ConceptNotExistException; import org.ohdsi.webapi.security.PermissionService; import org.ohdsi.webapi.service.dto.ConceptSetDTO; +import org.ohdsi.webapi.service.dto.ConceptSetMetaDataDTO; +import org.ohdsi.webapi.service.dto.MetaDataDTO; import org.ohdsi.webapi.shiro.Entities.UserEntity; import org.ohdsi.webapi.shiro.Entities.UserRepository; import org.ohdsi.webapi.shiro.management.Security; @@ -76,8 +80,6 @@ @Transactional @Path("/conceptset/") public class ConceptSetService extends AbstractDaoService implements HasTags { - @Autowired - private ConceptSetCriterionRepository criteriaRepository; @Autowired private ConceptSetGenerationInfoRepository conceptSetGenerationInfoRepository; @@ -109,12 +111,13 @@ public class ConceptSetService extends AbstractDaoService implements HasTags versionService; - @Value("${security.defaultGlobalReadPermissions}") + + @Value("${security.defaultGlobalReadPermissions}") private boolean defaultGlobalReadPermissions; public static final String COPY_NAME = "copyName"; - /** + /** * Get the concept set based in the identifier * * @summary Get concept set by ID @@ -465,15 +468,8 @@ public ConceptSetDTO createConceptSet(ConceptSetDTO conceptSetDTO) { updated.setCreatedBy(user); updated.setCreatedDate(new Date()); updated.setTags(null); - - // save criteria - ConceptSetCriterion criteria = new ConceptSetCriterion(); - criteria.setConceptSet(updated); - criteria.setId(updated.getId()); - criteria.setCriteria(conceptSetDTO.getCriteria()); - updated.setConceptSetCriterion(criteria); - ConceptSet conceptSetUpdate = updateConceptSet(updated, conceptSet); - return conversionService.convert(conceptSetUpdate, ConceptSetDTO.class); + updateConceptSet(updated, conceptSet); + return conversionService.convert(updated, ConceptSetDTO.class); } /** @@ -539,8 +535,7 @@ private ConceptSet updateConceptSet(ConceptSet dst, ConceptSet src) { dst.setDescription(src.getDescription()); dst.setModifiedDate(new Date()); dst.setModifiedBy(user); - dst.setConceptSetCriterion(dst.getConceptSetCriterion()); - + dst = this.getConceptSetRepository().save(dst); return dst; } @@ -581,10 +576,10 @@ private ConceptSetExport getConceptSetForExport(int conceptSetId, SourceInfo voc public Collection getConceptSetGenerationInfo(@PathParam("id") final int id) { return this.conceptSetGenerationInfoRepository.findAllByConceptSetId(id); } - + /** * Delete the selected concept set by concept set identifier - * + * * @summary Delete concept set * @param id The concept set ID */ @@ -602,7 +597,7 @@ public void deleteConceptSet(@PathParam("id") final int id) { catch (Exception e) { throw e; } - + // Remove the concept set items try { getConceptSetItemRepository().deleteByConceptSetId(id); @@ -695,10 +690,10 @@ public void unassignPermissionProtectedTag(@PathParam("id") final int id, @PathP } /** - * Checks a concept set for diagnostic problems. At this time, + * Checks a concept set for diagnostic problems. At this time, * this appears to be an endpoint used to check to see which tags * are applied to a concept set. - * + * * @summary Concept set tag check * @since v2.10.0 * @param conceptSetDTO The concept set @@ -869,4 +864,95 @@ private ConceptSetVersion saveVersion(int id) { version.setCreatedDate(versionDate); return versionService.create(VersionType.CONCEPT_SET, version); } + + /** + * Update the concept set metadata for each concept in concept set ID in the + * WebAPI database. + * + * The body has two parts: 1) the elements new concept which added to the + * concept set. 2) the elements concept which remove from concept set. + * + * @summary Create new or delete concept set metadata items + * @param id + * The concept set ID + * @param dto + * An object of 2 Array new metadata and remove metadata + * @return Boolean: true if the save is successful + */ + @PUT + @Path("/{id}/metadata") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + public boolean saveConceptSetMetaData(@PathParam("id") final int id, ConceptSetMetaDataDTO dto) { + UserEntity user = userRepository.findByLogin(security.getSubject()); + if (dto.getRemoveMetadata() != null && !dto.getRemoveMetadata().isEmpty()) { + for (MetaDataDTO metaDataDTO : dto.getRemoveMetadata()) { + this.getConceptSetMetaDataRepository().deleteMetadataByConceptSetIdAndConceptId(id, metaDataDTO.getConceptId()); + } +// getConceptSetMetaDataRepository().deleteMetadataByConceptSetIdAndInConceptId(id, +// dto.getRemoveMetadata().stream().map(MetaDataDTO::getConceptId).collect(Collectors.toList())); + } + ObjectMapper mapper = new ObjectMapper(); + if(dto.getNewMetadata() != null && !dto.getNewMetadata().isEmpty()) { + List metadataList = dto.getNewMetadata().stream().map(m -> { + ConceptSetMetaData metaData = new ConceptSetMetaData(); + metaData.setConceptSetId(id); + try { + metaData.setMetadata(mapper.writeValueAsString(m)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + metaData.setConceptId(m.getConceptId()); + metaData.setCreatedBy(user); + return metaData; + }).collect(Collectors.toList()); + + this.getConceptSetMetaDataRepository().save(metadataList); + } + + return true; + } + + @GET + @Path("/{id}/metadata") + @Produces(MediaType.APPLICATION_JSON) + public List getConceptSetMetaData(@PathParam("id") final int id) { + ObjectMapper mapper = new ObjectMapper(); + List metadataList = getConceptSetMetaDataRepository().findByConceptSetId(id); + List metadataDTOList = new ArrayList<>(); + for (ConceptSetMetaData metaData : metadataList) { + MetaDataDTO metaDataDTO = null; + try { + metaDataDTO = mapper.readValue(metaData.getMetadata(), MetaDataDTO.class); + metaDataDTO.setId(metaData.getId()); + metadataDTOList.add(metaDataDTO); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return metadataDTOList; + } + @DELETE + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteConceptSetMetaData(@PathParam("id") final int id) { + ConceptSetMetaData conceptSetMetaData = getConceptSetMetaDataRepository().findById(id); + if(conceptSetMetaData != null){ + getConceptSetMetaDataRepository().deleteById(id); + return Response.ok().build(); + }else throw new NotFoundException("Concept set metadata not found"); + } + + @PUT + @Path("/update/{id}/metadata") + @Produces(MediaType.APPLICATION_JSON) + public MetaDataDTO updateConceptSetMetaData(@PathParam("id") final int id, MetaDataDTO metaDataDTO) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ConceptSetMetaData metaData = getConceptSetMetaDataRepository() + .findConceptSetMetaDataByConceptIdAndConceptId(id, metaDataDTO.getConceptId()) + .orElseThrow(() -> new RuntimeException("Concept set metadata not found")); + metaData.setMetadata(mapper.writeValueAsString(metaDataDTO)); + getConceptSetMetaDataRepository().save(metaData); + return metaDataDTO; + } } diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java index 5e9d535725..1323d338ed 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetDTO.java @@ -5,7 +5,6 @@ public class ConceptSetDTO extends CommonEntityExtDTO { private Integer id; private String name; private String description; - private String criteria; public Integer getId() { return id; @@ -31,10 +30,4 @@ public void setDescription(String description) { this.description = description; } - public void setCriteria(String criteria) { - this.criteria = criteria; - } - public String getCriteria() { - return criteria; - } } diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java new file mode 100644 index 0000000000..9e62f9b650 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java @@ -0,0 +1,27 @@ +package org.ohdsi.webapi.service.dto; + +import java.util.List; + +public class ConceptSetMetaDataDTO { + + private List newMetadata; + + private List removeMetadata; + + public List getNewMetadata() { + return newMetadata; + } + + public void setNewMetadata(List newMetadata) { + this.newMetadata = newMetadata; + } + + public List getRemoveMetadata() { + return removeMetadata; + } + + public void setRemoveMetadata(List removeMetadata) { + this.removeMetadata = removeMetadata; + } + +} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java new file mode 100644 index 0000000000..72417a4670 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java @@ -0,0 +1,76 @@ +package org.ohdsi.webapi.service.dto; + +import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaDataRepository; + +public class MetaDataDTO { + private Integer id; + + private String searchData; + + private String relatedConcepts; + + private String conceptHierarchy; + + private String conceptSetData; + + private String conceptData; + + private Integer conceptId; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSearchData() { + return searchData; + } + + public void setSearchData(String searchData) { + this.searchData = searchData; + } + + public String getRelatedConcepts() { + return relatedConcepts; + } + + public void setRelatedConcepts(String relatedConcepts) { + this.relatedConcepts = relatedConcepts; + } + + public String getConceptHierarchy() { + return conceptHierarchy; + } + + public void setConceptHierarchy(String conceptHierarchy) { + this.conceptHierarchy = conceptHierarchy; + } + + public String getConceptSetData() { + return conceptSetData; + } + + public void setConceptSetData(String conceptSetData) { + this.conceptSetData = conceptSetData; + } + + public String getConceptData() { + return conceptData; + } + + public void setConceptData(String conceptData) { + this.conceptData = conceptData; + } + + public Integer getConceptId() { + return conceptId; + } + + public void setConceptId(Integer conceptId) { + this.conceptId = conceptId; + } + +} diff --git a/src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql b/src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql new file mode 100644 index 0000000000..51dfb697cc --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql @@ -0,0 +1,29 @@ +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:update:*:metadata:put', 'Update Concept Set Metadata'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:metadata:put', 'Create Concept Set Metadata'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:delete', 'Delete Concept Set Metadata'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:metadata:get', 'List Concept Set Metadata'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:metadata:get', 'View Concept Set Metadata'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:update:*:metadata:put', + 'conceptset:*:metadata:put', + 'conceptset:%s:delete', + 'conceptset:%s:metadata:get', + 'conceptset:*:metadata:get' + ) AND sr.name IN ('admin'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:%s:metadata:get', + 'conceptset:*:metadata:get' + ) AND sr.name IN ('Atlas users'); \ No newline at end of file diff --git a/src/main/resources/i18n/messages_en.json b/src/main/resources/i18n/messages_en.json index 5a247d4bb4..d465cc413c 100644 --- a/src/main/resources/i18n/messages_en.json +++ b/src/main/resources/i18n/messages_en.json @@ -1521,7 +1521,13 @@ "rcTooltip": "Record Count", "drcTooltip": "Descendant Record Count", "pcTooltip": "Person Count", - "dpcTooltip": "Descendant Person Count" + "dpcTooltip": "Descendant Person Count", + "conceptID": "Concept Id", + "searchData": "Search Data", + "relatedConcepts": "Related Concept", + "conceptHierarchy": "Concept Hierarchy", + "conceptSetData": "ConceptSet Data", + "conceptData": "Concept Data" }, "dataSources": { "const": { @@ -1786,7 +1792,8 @@ "includedConcepts": "Included Concepts", "includedSourceCodes": "Included Source Codes", "versions": "Versions", - "messages": "Messages" + "messages": "Messages", + "metadata": "Metadata" }, "concept": { "title": "Vocabulary > Concept", diff --git a/src/main/resources/i18n/messages_ko.json b/src/main/resources/i18n/messages_ko.json index 63fe140849..92a3d82e1e 100644 --- a/src/main/resources/i18n/messages_ko.json +++ b/src/main/resources/i18n/messages_ko.json @@ -1521,7 +1521,13 @@ "rcTooltip": "Record Count", "drcTooltip": "Descendant Record Count", "pcTooltip": "Person Count", - "dpcTooltip": "Descendant Person Count" + "dpcTooltip": "Descendant Person Count", + "conceptID": "개념 ID", + "searchData": "데이터 검색", + "relatedConcepts": "관련 개념", + "conceptHierarchy": "개념 계층 구조", + "conceptSetData": "ConceptSet 데이터", + "conceptData": "개념 데이터" }, "dataSources": { "const": { @@ -1786,7 +1792,8 @@ "includedConcepts": "포함된 컨셉", "includedSourceCodes": "포함된 소스 코드", "versions": "Versions", - "messages": "메시지" + "messages": "메시지", + "metadata": "Metadata" }, "concept": { "title": "어휘(Vocabulary) > 컨셉", diff --git a/src/main/resources/i18n/messages_ru.json b/src/main/resources/i18n/messages_ru.json index 6b576d00e3..522df62373 100644 --- a/src/main/resources/i18n/messages_ru.json +++ b/src/main/resources/i18n/messages_ru.json @@ -1521,7 +1521,13 @@ "pcTooltip": "Количество пациентов", "dpcTooltip": "Количество пациентов-потомков", "validStartDate": "Действительная дата начала", - "validEndDate": "Действительная дата конца" + "validEndDate": "Действительная дата конца", + "conceptID": "Идентификатор концепции", + "searchData": "Данные поиска", + "relatedConcepts": "Родственное понятие", + "conceptHierarchy": "Иерархия понятий", + "conceptSetData": "Данные ConceptSet", + "conceptData": "Концептуальные данные" }, "dataSources": { "headingTitle": "Источники данных", @@ -1696,7 +1702,8 @@ "includedConcepts": "Включенные концепции", "exploreEvidence": "Исследовать Свидетельство", "versions": "История версий", - "messages": "Сообщения" + "messages": "Сообщения", + "metadata": "Metadata" }, "exploreEvidence": { "title": "Исследование Свидетельства:", diff --git a/src/main/resources/i18n/messages_zh.json b/src/main/resources/i18n/messages_zh.json index bc0688ba71..f2ec36318c 100644 --- a/src/main/resources/i18n/messages_zh.json +++ b/src/main/resources/i18n/messages_zh.json @@ -1521,7 +1521,13 @@ "rcTooltip": "Record Count", "drcTooltip": "Descendant Record Count", "pcTooltip": "Person Count", - "dpcTooltip": "Descendant Person Count" + "dpcTooltip": "Descendant Person Count", + "conceptID": "概念 ID", + "searchData": "搜索数据", + "relatedConcepts":"相关概念", + "conceptHierarchy": "概念层次结构", + "conceptSetData": "概念集数据", + "conceptData": "概念数据" }, "dataSources": { "const": { @@ -1786,7 +1792,8 @@ "includedConcepts": "包含的概念", "includedSourceCodes": "包含的源代码", "versions": "Versions", - "messages": "提示信息" + "messages": "提示信息", + "metadata": "Metadata" }, "concept": { "title": "术语集 > 概念", From 757929ae7cb0af62acb21f3356e42ea3a5e66a41 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Tue, 4 Jun 2024 12:30:09 +0200 Subject: [PATCH 03/19] Fixing migration scripts and Concept Set Service to save and update metadata --- .../ohdsi/webapi/service/ConceptSetService.java | 7 +++++-- ...23121100__add_concept_set_meta_data_table.sql | 16 ++++++++++++++++ ...__conceptset_metadata_module_permissions.sql} | 0 ...14.0.2023121100_add_search_criteria_table.sql | 11 ----------- 4 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql rename src/main/resources/db/migration/postgresql/{V2.15.0.202404161000__conceptset_metadata_module_permissions.sql => V2.14.0.202404161000__conceptset_metadata_module_permissions.sql} (100%) delete mode 100644 src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 4e1a71a70c..d6f789f1d8 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.shiro.authz.UnauthorizedException; import org.ohdsi.circe.vocabulary.ConceptSetExpression; import org.ohdsi.vocabulary.Concept; @@ -884,7 +885,6 @@ private ConceptSetVersion saveVersion(int id) { @Produces(MediaType.APPLICATION_JSON) @Transactional public boolean saveConceptSetMetaData(@PathParam("id") final int id, ConceptSetMetaDataDTO dto) { - UserEntity user = userRepository.findByLogin(security.getSubject()); if (dto.getRemoveMetadata() != null && !dto.getRemoveMetadata().isEmpty()) { for (MetaDataDTO metaDataDTO : dto.getRemoveMetadata()) { this.getConceptSetMetaDataRepository().deleteMetadataByConceptSetIdAndConceptId(id, metaDataDTO.getConceptId()); @@ -903,7 +903,8 @@ public boolean saveConceptSetMetaData(@PathParam("id") final int id, ConceptSetM throw new RuntimeException(e); } metaData.setConceptId(m.getConceptId()); - metaData.setCreatedBy(user); + metaData.setCreatedBy(getCurrentUser()); + metaData.setCreatedDate(new Date()); return metaData; }).collect(Collectors.toList()); @@ -952,6 +953,8 @@ public MetaDataDTO updateConceptSetMetaData(@PathParam("id") final int id, MetaD .findConceptSetMetaDataByConceptIdAndConceptId(id, metaDataDTO.getConceptId()) .orElseThrow(() -> new RuntimeException("Concept set metadata not found")); metaData.setMetadata(mapper.writeValueAsString(metaDataDTO)); + metaData.setModifiedBy(getCurrentUser()); + metaData.setModifiedDate(new Date()); getConceptSetMetaDataRepository().save(metaData); return metaDataDTO; } diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql b/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql new file mode 100644 index 0000000000..e88371713e --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql @@ -0,0 +1,16 @@ +CREATE SEQUENCE ${ohdsiSchema}.concept_set_meta_data_sequence; + +CREATE TABLE ${ohdsiSchema}.concept_set_meta_data +( + concept_set_meta_data_id int4 NOT NULL DEFAULT nextval('${ohdsiSchema}.concept_set_meta_data_sequence'), + concept_set_id integer NOT NULL, + concept_id integer, + metadata varchar, + created_by_id INTEGER, + created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now()), + modified_by_id INTEGER, + modified_date TIMESTAMP WITH TIME ZONE, + CONSTRAINT pk_concept_set_meta_data_id PRIMARY KEY (concept_set_meta_data_id), + CONSTRAINT fk_concept_set FOREIGN KEY (concept_set_id) + REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) +); diff --git a/src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql similarity index 100% rename from src/main/resources/db/migration/postgresql/V2.15.0.202404161000__conceptset_metadata_module_permissions.sql rename to src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql diff --git a/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql b/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql deleted file mode 100644 index ad943c5980..0000000000 --- a/src/main/resources/db/migration/postgresql/v2.14.0.2023121100_add_search_criteria_table.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE ${ohdsiSchema}.concept_set_criteria -( - concept_set_id integer NOT NULL, - criteria character varying, - PRIMARY KEY (concept_set_id), - CONSTRAINT "conceptSetFk" FOREIGN KEY (concept_set_id) - REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) MATCH SIMPLE - ON UPDATE NO ACTION - ON DELETE NO ACTION - NOT VALID -); From 83579dd474f8f36eb8b02767a9955c85e335d9d8 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Fri, 28 Jun 2024 19:21:46 +0200 Subject: [PATCH 04/19] [ATL-17] Refactored the annotations feature. Renamed metadata to annotation, added vocabulary version and createdBy/createdDate --- .../annotation/ConceptSetAnnotation.java | 86 +++++ .../ConceptSetAnnotationRepository.java | 20 ++ .../metadata/ConceptSetMetaData.java | 76 ----- .../ConceptSetMetaDataRepository.java | 20 -- .../ConceptSetAnnotationPermissionSchema.java | 30 ++ .../ConceptSetMetadataPermissionSchema.java | 30 -- .../webapi/security/model/EntityType.java | 4 +- .../webapi/service/AbstractDaoService.java | 8 +- .../webapi/service/ConceptSetService.java | 309 +++++++++--------- .../webapi/service/dto/AnnotationDTO.java | 35 ++ .../service/dto/AnnotationDetailsDTO.java | 37 +++ .../service/dto/ConceptSetAnnotationDTO.java | 27 ++ .../service/dto/ConceptSetMetaDataDTO.java | 27 -- .../ohdsi/webapi/service/dto/MetaDataDTO.java | 76 ----- ...nceptset_rename_metadata_to_annotation.sql | 29 ++ src/main/resources/i18n/messages_en.json | 9 +- src/main/resources/i18n/messages_ko.json | 6 +- src/main/resources/i18n/messages_ru.json | 6 +- src/main/resources/i18n/messages_zh.json | 6 +- 19 files changed, 437 insertions(+), 404 deletions(-) create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java create mode 100644 src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotationRepository.java delete mode 100644 src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java delete mode 100644 src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java create mode 100644 src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java delete mode 100644 src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/AnnotationDetailsDTO.java create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java delete mode 100644 src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java delete mode 100644 src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java new file mode 100644 index 0000000000..c6bf9d4819 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -0,0 +1,86 @@ +package org.ohdsi.webapi.conceptset.annotation; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.ohdsi.webapi.model.CommonEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +@Entity(name = "ConceptSetAnnotation") +@Table(name = "concept_set_annotation") +public class ConceptSetAnnotation extends CommonEntity implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GenericGenerator( + name = "concept_set_annotation_generator", + strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", + parameters = { + @Parameter(name = "sequence_name", value = "concept_set_annotation_sequence"), + @Parameter(name = "increment_size", value = "1") + } + ) + @GeneratedValue(generator = "concept_set_annotation_generator") + @Column(name = "concept_set_annotation_id") + private Integer id; + + @Column(name = "concept_set_id", nullable = false) + private Integer conceptSetId; + + @Column(name = "concept_id") + private Integer conceptId; + + @Column(name = "annotation_details") + private String annotationDetails; + + @Column(name = "vocabulary_version") + private String vocabularyVersion; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getConceptSetId() { + return conceptSetId; + } + + public void setConceptSetId(Integer conceptSetId) { + this.conceptSetId = conceptSetId; + } + + public Integer getConceptId() { + return conceptId; + } + + public void setConceptId(Integer conceptId) { + this.conceptId = conceptId; + } + + public String getAnnotationDetails() { + return annotationDetails; + } + + public void setAnnotationDetails(String annotationDetails) { + this.annotationDetails = annotationDetails; + } + + public String getVocabularyVersion() { + return vocabularyVersion; + } + + public void setVocabularyVersion(String vocabularyVersion) { + this.vocabularyVersion = vocabularyVersion; + } +} diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotationRepository.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotationRepository.java new file mode 100644 index 0000000000..f44bd01c72 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotationRepository.java @@ -0,0 +1,20 @@ +package org.ohdsi.webapi.conceptset.annotation; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ConceptSetAnnotationRepository extends JpaRepository { + + @Query("DELETE FROM ConceptSetAnnotation cc WHERE cc.conceptSetId = :conceptSetId and cc.conceptId in :conceptId") + void deleteAnnotationByConceptSetIdAndInConceptId(int conceptSetId, List conceptId); + + void deleteAnnotationByConceptSetIdAndConceptId(int conceptSetId, int conceptId); + + List findByConceptSetId(int conceptSetId); + ConceptSetAnnotation findById(int id); + void deleteById(int id); + Optional findConceptSetAnnotationByConceptIdAndConceptId(int conceptSetId, int conceptId); +} diff --git a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java deleted file mode 100644 index 6b23f032ca..0000000000 --- a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaData.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.ohdsi.webapi.conceptset.metadata; - -import java.io.Serializable; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; - -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Parameter; -import org.ohdsi.webapi.model.CommonEntity; - -@Entity(name = "ConceptSetMetaData") -@Table(name = "concept_set_meta_data") -public class ConceptSetMetaData extends CommonEntity implements Serializable { - /** - * - */ - private static final long serialVersionUID = 1L; - - @Id - @GenericGenerator( - name = "concept_set_meta_data_generator", - strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", - parameters = { - @Parameter(name = "sequence_name", value = "concept_set_meta_data_sequence"), - @Parameter(name = "increment_size", value = "1") - } - ) - @GeneratedValue(generator = "concept_set_meta_data_generator") - @Column(name="concept_set_meta_data_id") - private Integer id; - - @Column(name = "concept_set_id", nullable = false) - private Integer conceptSetId; - - @Column(name = "concept_id") - private Integer conceptId; - - @Column(name = "metadata") - private String metadata; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public Integer getConceptSetId() { - return conceptSetId; - } - - public void setConceptSetId(Integer conceptSetId) { - this.conceptSetId = conceptSetId; - } - - public Integer getConceptId() { - return conceptId; - } - - public void setConceptId(Integer conceptId) { - this.conceptId = conceptId; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} diff --git a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java b/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java deleted file mode 100644 index 82f6494981..0000000000 --- a/src/main/java/org/ohdsi/webapi/conceptset/metadata/ConceptSetMetaDataRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ohdsi.webapi.conceptset.metadata; - -import java.util.List; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface ConceptSetMetaDataRepository extends JpaRepository { - - @Query("DELETE FROM ConceptSetMetaData cc WHERE cc.conceptSetId = :conceptSetId and cc.conceptId in :conceptId") - void deleteMetadataByConceptSetIdAndInConceptId(int conceptSetId, List conceptId); - - void deleteMetadataByConceptSetIdAndConceptId(int conceptSetId, int conceptId); - - List findByConceptSetId(int conceptSetId); - ConceptSetMetaData findById(int id); - void deleteById(int id); - Optional findConceptSetMetaDataByConceptIdAndConceptId(int conceptSetId, int conceptId); -} diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java new file mode 100644 index 0000000000..c2eea4141a --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java @@ -0,0 +1,30 @@ +package org.ohdsi.webapi.security.model; + +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class ConceptSetAnnotationPermissionSchema extends EntityPermissionSchema { + + private static Map writePermissions = new HashMap() { + { + put("conceptset:*:annotation:put", "Create Concept Set Annotation"); + put("conceptset:update:*:annotation:put", "Update Concept Set Annotation"); + put("conceptset:%s:annotation:delete", "Delete Concept Set Annotation with ID %s"); + } + }; + + private static Map readPermissions = new HashMap() { + { + put("conceptset:%s:annotation:get", "List Concept Set Annotation with id %s"); + put("conceptset:*:annotation:get", "View Concept Set Annotation expression"); + } + }; + + public ConceptSetAnnotationPermissionSchema() { + + super(EntityType.CONCEPT_SET_ANNOTATION, readPermissions, writePermissions); + } +} diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java deleted file mode 100644 index c3e7605aee..0000000000 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetMetadataPermissionSchema.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.ohdsi.webapi.security.model; - -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -@Component -public class ConceptSetMetadataPermissionSchema extends EntityPermissionSchema { - - private static Map writePermissions = new HashMap() { - { - put("conceptset:*:metadata:put", "Create Concept Set Metadata"); - put("conceptset:update:*:metadata:put", "Update Concept Set Metadata"); - put("conceptset:%s:metadata:delete", "Delete Concept Set Metadata with ID %s"); - } - }; - - private static Map readPermissions = new HashMap() { - { - put("conceptset:%s:metadata:get", "List Concept Set Metadata with id %s"); - put("conceptset:*:metadata:get", "View Concept Set Metadata expression"); - } - }; - - public ConceptSetMetadataPermissionSchema() { - - super(EntityType.CONCEPT_SET_METADATA, readPermissions, writePermissions); - } -} diff --git a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java index 3ef71b0551..b80e9e7e44 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java @@ -4,7 +4,7 @@ import org.ohdsi.webapi.cohortdefinition.CohortDefinition; import org.ohdsi.webapi.cohortsample.CohortSample; import org.ohdsi.webapi.conceptset.ConceptSet; -import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaData; +import org.ohdsi.webapi.conceptset.annotation.ConceptSetAnnotation; import org.ohdsi.webapi.estimation.Estimation; import org.ohdsi.webapi.feanalysis.domain.FeAnalysisEntity; import org.ohdsi.webapi.ircalc.IncidenceRateAnalysis; @@ -28,7 +28,7 @@ public enum EntityType { COHORT_SAMPLE(CohortSample.class), TAG(Tag.class), REUSABLE(Reusable.class), - CONCEPT_SET_METADATA(ConceptSetMetaData .class); + CONCEPT_SET_ANNOTATION(ConceptSetAnnotation.class); private final Class entityClass; diff --git a/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java b/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java index 12df361557..b84ef71890 100644 --- a/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java +++ b/src/main/java/org/ohdsi/webapi/service/AbstractDaoService.java @@ -20,7 +20,7 @@ import org.ohdsi.webapi.conceptset.ConceptSetComparison; import org.ohdsi.webapi.conceptset.ConceptSetItemRepository; import org.ohdsi.webapi.conceptset.ConceptSetRepository; -import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaDataRepository; +import org.ohdsi.webapi.conceptset.annotation.ConceptSetAnnotationRepository; import org.ohdsi.webapi.exception.BadRequestAtlasException; import org.ohdsi.webapi.ircalc.IncidenceRateAnalysis; import org.ohdsi.webapi.model.CommonEntity; @@ -108,7 +108,7 @@ public abstract class AbstractDaoService extends AbstractAdminService { private ConceptSetItemRepository conceptSetItemRepository; @Autowired - private ConceptSetMetaDataRepository conceptSetMetaDataRepository; + private ConceptSetAnnotationRepository conceptSetAnnotationRepository; @Autowired protected Security security; @@ -124,8 +124,8 @@ public abstract class AbstractDaoService extends AbstractAdminService { public ConceptSetItemRepository getConceptSetItemRepository() { return conceptSetItemRepository; } - public ConceptSetMetaDataRepository getConceptSetMetaDataRepository() { - return conceptSetMetaDataRepository; + public ConceptSetAnnotationRepository getConceptSetAnnotationRepository() { + return conceptSetAnnotationRepository; } @Autowired diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index d6f789f1d8..99780caffd 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -40,12 +40,13 @@ import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfoRepository; import org.ohdsi.webapi.conceptset.ConceptSetItem; import org.ohdsi.webapi.conceptset.dto.ConceptSetVersionFullDTO; -import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaData; +import org.ohdsi.webapi.conceptset.annotation.ConceptSetAnnotation; import org.ohdsi.webapi.exception.ConceptNotExistException; import org.ohdsi.webapi.security.PermissionService; +import org.ohdsi.webapi.service.dto.AnnotationDetailsDTO; import org.ohdsi.webapi.service.dto.ConceptSetDTO; -import org.ohdsi.webapi.service.dto.ConceptSetMetaDataDTO; -import org.ohdsi.webapi.service.dto.MetaDataDTO; +import org.ohdsi.webapi.service.dto.ConceptSetAnnotationDTO; +import org.ohdsi.webapi.service.dto.AnnotationDTO; import org.ohdsi.webapi.shiro.Entities.UserEntity; import org.ohdsi.webapi.shiro.Entities.UserRepository; import org.ohdsi.webapi.shiro.management.Security; @@ -71,12 +72,12 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Component; - /** - * Provides REST services for working with - * concept sets. - * - * @summary Concept Set - */ +/** + * Provides REST services for working with + * concept sets. + * + * @summary Concept Set + */ @Component @Transactional @Path("/conceptset/") @@ -113,14 +114,14 @@ public class ConceptSetService extends AbstractDaoService implements HasTags versionService; - @Value("${security.defaultGlobalReadPermissions}") + @Value("${security.defaultGlobalReadPermissions}") private boolean defaultGlobalReadPermissions; - + public static final String COPY_NAME = "copyName"; - /** + /** * Get the concept set based in the identifier - * + * * @summary Get concept set by ID * @param id The concept set ID * @return The concept set definition @@ -136,7 +137,7 @@ public ConceptSetDTO getConceptSet(@PathParam("id") final int id) { /** * Get the full list of concept sets in the WebAPI database - * + * * @summary Get all concept sets * @return A list of all concept sets in the WebAPI database */ @@ -159,7 +160,7 @@ public Collection getConceptSets() { /** * Get the concept set items for a selected concept set ID. - * + * * @summary Get the concept set items * @param id The concept set identifier * @return A list of concept set items @@ -173,7 +174,7 @@ public Iterable getConceptSetItems(@PathParam("id") final int id /** * Get the concept set expression for a selected version of the expression - * + * * @summary Get concept set expression by version * @param id The concept set ID * @param version The version identifier @@ -196,7 +197,7 @@ public ConceptSetExpression getConceptSetExpression(@PathParam("id") final int i * source key. NOTE: This method requires the specification * of a source key but it does not appear to be used by the underlying * code. - * + * * @summary Get concept set expression by version and source. * @param id The concept set identifier * @param version The version of the concept set @@ -218,7 +219,7 @@ public ConceptSetExpression getConceptSetExpression(@PathParam("id") final int i /** * Get the concept set expression by identifier - * + * * @summary Get concept set by ID * @param id The concept set identifier * @return The concept set expression @@ -236,7 +237,7 @@ public ConceptSetExpression getConceptSetExpression(@PathParam("id") final int i /** * Get the concept set expression by identifier and source key - * + * * @summary Get concept set by ID and source * @param id The concept set ID * @param sourceKey The source key @@ -296,26 +297,26 @@ private ConceptSetExpression getConceptSetExpression(int id, Integer version, So throw new ConceptNotExistException("Current data source does not contain required concepts " + ids); } for(Concept concept : concepts) { - map.put(concept.conceptId, concept); // associate the concept object to the conceptID in the map + map.put(concept.conceptId, concept); // associate the concept object to the conceptID in the map } - // put the concept information into the expression along with the concept set item information + // put the concept information into the expression along with the concept set item information for (ConceptSetItem repositoryItem : repositoryItems) { - ConceptSetExpression.ConceptSetItem currentItem = new ConceptSetExpression.ConceptSetItem(); - currentItem.concept = map.get(repositoryItem.getConceptId()); - currentItem.includeDescendants = (repositoryItem.getIncludeDescendants() == 1); - currentItem.includeMapped = (repositoryItem.getIncludeMapped() == 1); - currentItem.isExcluded = (repositoryItem.getIsExcluded() == 1); - expressionItems.add(currentItem); + ConceptSetExpression.ConceptSetItem currentItem = new ConceptSetExpression.ConceptSetItem(); + currentItem.concept = map.get(repositoryItem.getConceptId()); + currentItem.includeDescendants = (repositoryItem.getIncludeDescendants() == 1); + currentItem.includeMapped = (repositoryItem.getIncludeMapped() == 1); + currentItem.isExcluded = (repositoryItem.getIsExcluded() == 1); + expressionItems.add(currentItem); } expression.items = expressionItems.toArray(new ConceptSetExpression.ConceptSetItem[0]); // this will return a new array - + return expression; } /** * Check if the concept set name exists (DEPRECATED) - * + * * @summary DO NOT USE * @deprecated * @param id The concept set ID @@ -336,11 +337,11 @@ public Response getConceptSetExistsDeprecated(@PathParam("id") final int id, @Pa * Check if a concept set with the same name exists in the WebAPI * database. The name is checked against the selected concept set ID * to ensure that only the selected concept set ID has the name specified. - * + * * @summary Concept set with same name exists * @param id The concept set ID * @param name The name of the concept set - * @return The count of concept sets with the name, excluding the + * @return The count of concept sets with the name, excluding the * specified concept set ID. */ @GET @@ -353,11 +354,11 @@ public int getCountCSetWithSameName(@PathParam("id") @DefaultValue("0") final in /** * Update the concept set items for the selected concept set ID in the * WebAPI database. - * + * * The concept set has two parts: 1) the elements of the ConceptSetDTO that - * consist of the identifier, name, etc. 2) the concept set items which + * consist of the identifier, name, etc. 2) the concept set items which * contain the concepts and their mapping (i.e. include descendants). - * + * * @summary Update concept set items * @param id The concept set ID * @param items An array of ConceptSetItems @@ -384,12 +385,12 @@ public boolean saveConceptSetItems(@PathParam("id") final int id, ConceptSetItem * Exports a list of concept sets, based on the conceptSetList argument, * to one or more comma separated value (CSV) file(s), compresses the files * into a ZIP file and sends the ZIP file to the client. - * + * * @summary Export concept set list to CSV files * @param conceptSetList A list of concept set identifiers in the format * conceptset=++ * @return - * @throws Exception + * @throws Exception */ @GET @Path("/exportlist") @@ -419,7 +420,7 @@ public Response exportConceptSetList(@QueryParam("conceptsets") final String con // Get the concept set information cs.add(getConceptSetForExport(conceptSetIds.get(i), new SourceInfo(source))); } - // Write Concept Set Expression to a CSV + // Write Concept Set Expression to a CSV baos = ExportUtil.writeConceptSetExportToCSVAndZip(cs); response = Response @@ -435,12 +436,12 @@ public Response exportConceptSetList(@QueryParam("conceptsets") final String con } /** - * Exports a single concept set to a comma separated value (CSV) + * Exports a single concept set to a comma separated value (CSV) * file, compresses to a ZIP file and sends to the client. * @param id The concept set ID * @return A zip file containing the exported concept set - * @throws Exception + * @throws Exception */ @GET @Path("{id}/export") @@ -452,7 +453,7 @@ public Response exportConceptSetToCSV(@PathParam("id") final String id) throws E /** * Save a new concept set to the WebAPI database - * + * * @summary Create a new concept set * @param conceptSetDTO The concept set to save * @return The concept set saved with the concept set identifier @@ -478,7 +479,7 @@ public ConceptSetDTO createConceptSet(ConceptSetDTO conceptSetDTO) { * that is used when generating a copy of an existing concept set. This * function is generally used in conjunction with the copy endpoint to * create a unique name and then save a copy of an existing concept set. - * + * * @sumamry Get concept set name suggestion for copying * @param id The concept set ID * @return A map of the new concept set name and the existing concept set @@ -500,17 +501,17 @@ public List getNamesLike(String copyName) { /** * Updates the concept set for the selected concept set. - * + * * The concept set has two parts: 1) the elements of the ConceptSetDTO that - * consist of the identifier, name, etc. 2) the concept set items which + * consist of the identifier, name, etc. 2) the concept set items which * contain the concepts and their mapping (i.e. include descendants). - * + * * @summary Update concept set * @param id The concept set identifier * @param conceptSetDTO The concept set header * @return The - * @throws Exception - */ + * @throws Exception + */ @Path("/{id}") @PUT @Consumes(MediaType.APPLICATION_JSON) @@ -520,7 +521,7 @@ public ConceptSetDTO updateConceptSet(@PathParam("id") final int id, ConceptSetD ConceptSet updated = getConceptSetRepository().findById(id); if (updated == null) { - throw new Exception("Concept Set does not exist."); + throw new Exception("Concept Set does not exist."); } saveVersion(id); @@ -540,7 +541,7 @@ private ConceptSet updateConceptSet(ConceptSet dst, ConceptSet src) { dst = this.getConceptSetRepository().save(dst); return dst; } - + private ConceptSetExport getConceptSetForExport(int conceptSetId, SourceInfo vocabSource) { ConceptSetExport cs = new ConceptSetExport(); @@ -564,63 +565,63 @@ private ConceptSetExport getConceptSetForExport(int conceptSetId, SourceInfo voc * Get the concept set generation information for the selected concept * set ID. This function only works with the configuration of the CEM * data source. - * + * * @link https://github.com/OHDSI/CommonEvidenceModel/wiki - * + * * @summary Get concept set generation info * @param id The concept set identifier. * @return A collection of concept set generation info objects */ - @GET - @Path("{id}/generationinfo") - @Produces(MediaType.APPLICATION_JSON) - public Collection getConceptSetGenerationInfo(@PathParam("id") final int id) { - return this.conceptSetGenerationInfoRepository.findAllByConceptSetId(id); - } - - /** - * Delete the selected concept set by concept set identifier - * - * @summary Delete concept set - * @param id The concept set ID - */ - @DELETE - @Transactional(rollbackOn = Exception.class, dontRollbackOn = EmptyResultDataAccessException.class) - @Path("{id}") - public void deleteConceptSet(@PathParam("id") final int id) { - // Remove any generation info - try { - this.conceptSetGenerationInfoRepository.deleteByConceptSetId(id); - } catch (EmptyResultDataAccessException e) { - // Ignore - there may be no data - log.warn("Failed to delete Generation Info by ConceptSet with ID = {}, {}", id, e); - } - catch (Exception e) { - throw e; - } - - // Remove the concept set items - try { - getConceptSetItemRepository().deleteByConceptSetId(id); - } catch (EmptyResultDataAccessException e) { - // Ignore - there may be no data - log.warn("Failed to delete ConceptSet items with ID = {}, {}", id, e); - } - catch (Exception e) { - throw e; - } - - // Remove the concept set - try { - getConceptSetRepository().delete(id); - } catch (EmptyResultDataAccessException e) { - // Ignore - there may be no data - log.warn("Failed to delete ConceptSet with ID = {}, {}", id, e); - } - catch (Exception e) { - throw e; - } - } + @GET + @Path("{id}/generationinfo") + @Produces(MediaType.APPLICATION_JSON) + public Collection getConceptSetGenerationInfo(@PathParam("id") final int id) { + return this.conceptSetGenerationInfoRepository.findAllByConceptSetId(id); + } + + /** + * Delete the selected concept set by concept set identifier + * + * @summary Delete concept set + * @param id The concept set ID + */ + @DELETE + @Transactional(rollbackOn = Exception.class, dontRollbackOn = EmptyResultDataAccessException.class) + @Path("{id}") + public void deleteConceptSet(@PathParam("id") final int id) { + // Remove any generation info + try { + this.conceptSetGenerationInfoRepository.deleteByConceptSetId(id); + } catch (EmptyResultDataAccessException e) { + // Ignore - there may be no data + log.warn("Failed to delete Generation Info by ConceptSet with ID = {}, {}", id, e); + } + catch (Exception e) { + throw e; + } + + // Remove the concept set items + try { + getConceptSetItemRepository().deleteByConceptSetId(id); + } catch (EmptyResultDataAccessException e) { + // Ignore - there may be no data + log.warn("Failed to delete ConceptSet items with ID = {}, {}", id, e); + } + catch (Exception e) { + throw e; + } + + // Remove the concept set + try { + getConceptSetRepository().delete(id); + } catch (EmptyResultDataAccessException e) { + // Ignore - there may be no data + log.warn("Failed to delete ConceptSet with ID = {}, {}", id, e); + } + catch (Exception e) { + throw e; + } + } /** * Assign tag to Concept Set @@ -867,95 +868,99 @@ private ConceptSetVersion saveVersion(int id) { } /** - * Update the concept set metadata for each concept in concept set ID in the + * Update the concept set annotation for each concept in concept set ID in the * WebAPI database. - * + *

* The body has two parts: 1) the elements new concept which added to the * concept set. 2) the elements concept which remove from concept set. * - * @summary Create new or delete concept set metadata items - * @param id - * The concept set ID - * @param dto - * An object of 2 Array new metadata and remove metadata + * @param id The concept set ID + * @param dto An object of 2 Array new annotation and remove annotation * @return Boolean: true if the save is successful + * @summary Create new or delete concept set annotation items */ @PUT - @Path("/{id}/metadata") + @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) @Transactional - public boolean saveConceptSetMetaData(@PathParam("id") final int id, ConceptSetMetaDataDTO dto) { - if (dto.getRemoveMetadata() != null && !dto.getRemoveMetadata().isEmpty()) { - for (MetaDataDTO metaDataDTO : dto.getRemoveMetadata()) { - this.getConceptSetMetaDataRepository().deleteMetadataByConceptSetIdAndConceptId(id, metaDataDTO.getConceptId()); + public boolean saveConceptSetAnnotation(@PathParam("id") final int id, ConceptSetAnnotationDTO dto) { + if (dto.getRemoveAnnotation() != null && !dto.getRemoveAnnotation().isEmpty()) { + for (AnnotationDTO annotationDTO : dto.getRemoveAnnotation()) { + this.getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndConceptId(id, annotationDTO.getConceptId()); } -// getConceptSetMetaDataRepository().deleteMetadataByConceptSetIdAndInConceptId(id, -// dto.getRemoveMetadata().stream().map(MetaDataDTO::getConceptId).collect(Collectors.toList())); +// getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndInConceptId(id, +// dto.getRemoveAnnotation().stream().map(AnnotationDTO::getConceptId).collect(Collectors.toList())); } ObjectMapper mapper = new ObjectMapper(); - if(dto.getNewMetadata() != null && !dto.getNewMetadata().isEmpty()) { - List metadataList = dto.getNewMetadata().stream().map(m -> { - ConceptSetMetaData metaData = new ConceptSetMetaData(); - metaData.setConceptSetId(id); + if (dto.getNewAnnotation() != null && !dto.getNewAnnotation().isEmpty()) { + List annotationList = dto.getNewAnnotation().stream().map(m -> { + ConceptSetAnnotation conceptSetAnnotation = new ConceptSetAnnotation(); + conceptSetAnnotation.setConceptSetId(id); try { - metaData.setMetadata(mapper.writeValueAsString(m)); + AnnotationDetailsDTO annotationDetailsDTO = mapper.readValue(mapper.writeValueAsString(m), AnnotationDetailsDTO.class); + conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDetailsDTO)); } catch (JsonProcessingException e) { throw new RuntimeException(e); } - metaData.setConceptId(m.getConceptId()); - metaData.setCreatedBy(getCurrentUser()); - metaData.setCreatedDate(new Date()); - return metaData; + conceptSetAnnotation.setVocabularyVersion(m.getVocabularyVersion()); + conceptSetAnnotation.setConceptId(m.getConceptId()); + conceptSetAnnotation.setCreatedBy(getCurrentUser()); + conceptSetAnnotation.setCreatedDate(new Date()); + return conceptSetAnnotation; }).collect(Collectors.toList()); - this.getConceptSetMetaDataRepository().save(metadataList); + this.getConceptSetAnnotationRepository().save(annotationList); } return true; } - @GET - @Path("/{id}/metadata") + @GET + @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) - public List getConceptSetMetaData(@PathParam("id") final int id) { + public List getConceptSetAnnotation(@PathParam("id") final int id) { ObjectMapper mapper = new ObjectMapper(); - List metadataList = getConceptSetMetaDataRepository().findByConceptSetId(id); - List metadataDTOList = new ArrayList<>(); - for (ConceptSetMetaData metaData : metadataList) { - MetaDataDTO metaDataDTO = null; + List annotationList = getConceptSetAnnotationRepository().findByConceptSetId(id); + List annotationDTOList = new ArrayList<>(); + for (ConceptSetAnnotation conceptSetAnnotation : annotationList) { + AnnotationDTO annotationDTO = null; try { - metaDataDTO = mapper.readValue(metaData.getMetadata(), MetaDataDTO.class); - metaDataDTO.setId(metaData.getId()); - metadataDTOList.add(metaDataDTO); + annotationDTO = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDTO.class); + annotationDTO.setId(conceptSetAnnotation.getId()); + annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); + annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); + annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); + annotationDTOList.add(annotationDTO); } catch (IOException e) { throw new RuntimeException(e); } } - return metadataDTOList; + return annotationDTOList; } + @DELETE @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) - public Response deleteConceptSetMetaData(@PathParam("id") final int id) { - ConceptSetMetaData conceptSetMetaData = getConceptSetMetaDataRepository().findById(id); - if(conceptSetMetaData != null){ - getConceptSetMetaDataRepository().deleteById(id); + public Response deleteConceptSetAnnotation(@PathParam("id") final int id) { + ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository().findById(id); + if (conceptSetAnnotation != null) { + getConceptSetAnnotationRepository().deleteById(id); return Response.ok().build(); - }else throw new NotFoundException("Concept set metadata not found"); + } else throw new NotFoundException("Concept set annotation not found"); } @PUT - @Path("/update/{id}/metadata") + @Path("/update/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) - public MetaDataDTO updateConceptSetMetaData(@PathParam("id") final int id, MetaDataDTO metaDataDTO) throws IOException { + public AnnotationDTO updateConceptSetAnnotation(@PathParam("id") final int id, AnnotationDTO annotationDTO) throws IOException { ObjectMapper mapper = new ObjectMapper(); - ConceptSetMetaData metaData = getConceptSetMetaDataRepository() - .findConceptSetMetaDataByConceptIdAndConceptId(id, metaDataDTO.getConceptId()) - .orElseThrow(() -> new RuntimeException("Concept set metadata not found")); - metaData.setMetadata(mapper.writeValueAsString(metaDataDTO)); - metaData.setModifiedBy(getCurrentUser()); - metaData.setModifiedDate(new Date()); - getConceptSetMetaDataRepository().save(metaData); - return metaDataDTO; - } -} + ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository() + .findConceptSetAnnotationByConceptIdAndConceptId(id, annotationDTO.getConceptId()) + .orElseThrow(() -> new RuntimeException("Concept set annotation not found")); + conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDTO)); + conceptSetAnnotation.setModifiedBy(getCurrentUser()); + conceptSetAnnotation.setModifiedDate(new Date()); + getConceptSetAnnotationRepository().save(conceptSetAnnotation); + return annotationDTO; + } +} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java new file mode 100644 index 0000000000..ea9c3d2715 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java @@ -0,0 +1,35 @@ +package org.ohdsi.webapi.service.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AnnotationDTO extends AnnotationDetailsDTO { + + private String createdBy; + private String createdDate; + private String vocabularyVersion; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public String getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(String createdDate) { + this.createdDate = createdDate; + } + + public String getVocabularyVersion() { + return vocabularyVersion; + } + + public void setVocabularyVersion(String vocabularyVersion) { + this.vocabularyVersion = vocabularyVersion; + } +} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDetailsDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDetailsDTO.java new file mode 100644 index 0000000000..d62acb2493 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDetailsDTO.java @@ -0,0 +1,37 @@ +package org.ohdsi.webapi.service.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AnnotationDetailsDTO { + private Integer id; + + private String searchData; + + private Integer conceptId; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSearchData() { + return searchData; + } + + public void setSearchData(String searchData) { + this.searchData = searchData; + } + + public Integer getConceptId() { + return conceptId; + } + + public void setConceptId(Integer conceptId) { + this.conceptId = conceptId; + } + +} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java new file mode 100644 index 0000000000..410f25ccb4 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java @@ -0,0 +1,27 @@ +package org.ohdsi.webapi.service.dto; + +import java.util.List; + +public class ConceptSetAnnotationDTO { + + private List newAnnotation; + + private List removeAnnotation; + + public List getNewAnnotation() { + return newAnnotation; + } + + public void setNewAnnotation(List newAnnotation) { + this.newAnnotation = newAnnotation; + } + + public List getRemoveAnnotation() { + return removeAnnotation; + } + + public void setRemoveAnnotation(List removeAnnotation) { + this.removeAnnotation = removeAnnotation; + } + +} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java deleted file mode 100644 index 9e62f9b650..0000000000 --- a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetMetaDataDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ohdsi.webapi.service.dto; - -import java.util.List; - -public class ConceptSetMetaDataDTO { - - private List newMetadata; - - private List removeMetadata; - - public List getNewMetadata() { - return newMetadata; - } - - public void setNewMetadata(List newMetadata) { - this.newMetadata = newMetadata; - } - - public List getRemoveMetadata() { - return removeMetadata; - } - - public void setRemoveMetadata(List removeMetadata) { - this.removeMetadata = removeMetadata; - } - -} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java deleted file mode 100644 index 72417a4670..0000000000 --- a/src/main/java/org/ohdsi/webapi/service/dto/MetaDataDTO.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.ohdsi.webapi.service.dto; - -import org.ohdsi.webapi.conceptset.metadata.ConceptSetMetaDataRepository; - -public class MetaDataDTO { - private Integer id; - - private String searchData; - - private String relatedConcepts; - - private String conceptHierarchy; - - private String conceptSetData; - - private String conceptData; - - private Integer conceptId; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getSearchData() { - return searchData; - } - - public void setSearchData(String searchData) { - this.searchData = searchData; - } - - public String getRelatedConcepts() { - return relatedConcepts; - } - - public void setRelatedConcepts(String relatedConcepts) { - this.relatedConcepts = relatedConcepts; - } - - public String getConceptHierarchy() { - return conceptHierarchy; - } - - public void setConceptHierarchy(String conceptHierarchy) { - this.conceptHierarchy = conceptHierarchy; - } - - public String getConceptSetData() { - return conceptSetData; - } - - public void setConceptSetData(String conceptSetData) { - this.conceptSetData = conceptSetData; - } - - public String getConceptData() { - return conceptData; - } - - public void setConceptData(String conceptData) { - this.conceptData = conceptData; - } - - public Integer getConceptId() { - return conceptId; - } - - public void setConceptId(Integer conceptId) { - this.conceptId = conceptId; - } - -} diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql new file mode 100644 index 0000000000..2d4178a9e6 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql @@ -0,0 +1,29 @@ +ALTER SEQUENCE ${ohdsiSchema}.concept_set_meta_data_sequence RENAME TO concept_set_annotation_sequence; + +ALTER TABLE ${ohdsiSchema}.concept_set_meta_data RENAME TO concept_set_annotation; + +ALTER TABLE ${ohdsiSchema}.concept_set_annotation RENAME COLUMN concept_set_meta_data_id TO concept_set_annotation_id; +ALTER TABLE ${ohdsiSchema}.concept_set_annotation RENAME COLUMN metadata TO annotation_details; + +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:update:*:annotation:put', description = 'Update Concept Set Annotation' +WHERE value = 'conceptset:update:*:metadata:put' AND description = 'Update Concept Set Metadata'; + +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:*:annotation:put', description = 'Create Concept Set Annotation' +WHERE value = 'conceptset:*:metadata:put' AND description = 'Create Concept Set Metadata'; + +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:%s:delete', description = 'Delete Concept Set Annotation' +WHERE value = 'conceptset:%s:delete' AND description = 'Delete Concept Set Metadata'; + +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:%s:annotation:get', description = 'List Concept Set Annotation' +WHERE value = 'conceptset:%s:metadata:get' AND description = 'List Concept Set Metadata'; + +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:*:annotation:get', description = 'View Concept Set Annotation' +WHERE value = 'conceptset:*:metadata:get' AND description = 'View Concept Set Metadata'; + +ALTER TABLE ${ohdsiSchema}.concept_set_annotation +ADD COLUMN vocabulary_version VARCHAR; \ No newline at end of file diff --git a/src/main/resources/i18n/messages_en.json b/src/main/resources/i18n/messages_en.json index d465cc413c..f4642dcd47 100644 --- a/src/main/resources/i18n/messages_en.json +++ b/src/main/resources/i18n/messages_en.json @@ -1524,10 +1524,8 @@ "dpcTooltip": "Descendant Person Count", "conceptID": "Concept Id", "searchData": "Search Data", - "relatedConcepts": "Related Concept", - "conceptHierarchy": "Concept Hierarchy", - "conceptSetData": "ConceptSet Data", - "conceptData": "Concept Data" + "createdBy": "Created By", + "createdDate": "Created Date" }, "dataSources": { "const": { @@ -2453,7 +2451,8 @@ "alerts": { "clearLocalCache": "Local Storage has been cleared. Please refresh the page to reload configuration information.", "clearServerCache": "Server cache has been cleared.", - "failUpdatePrioritySourceDaimon": "Failed to update priority source daimon" + "failUpdatePrioritySourceDaimon": "Failed to update priority source daimon", + "failUpdateCurrentVocabVersion": "Failed to update current vocabulary version" }, "buttons": { "check": "check", diff --git a/src/main/resources/i18n/messages_ko.json b/src/main/resources/i18n/messages_ko.json index 92a3d82e1e..96775b83f0 100644 --- a/src/main/resources/i18n/messages_ko.json +++ b/src/main/resources/i18n/messages_ko.json @@ -1524,10 +1524,8 @@ "dpcTooltip": "Descendant Person Count", "conceptID": "개념 ID", "searchData": "데이터 검색", - "relatedConcepts": "관련 개념", - "conceptHierarchy": "개념 계층 구조", - "conceptSetData": "ConceptSet 데이터", - "conceptData": "개념 데이터" + "createdBy": "만든 사람", + "createdDate": "생성 날짜" }, "dataSources": { "const": { diff --git a/src/main/resources/i18n/messages_ru.json b/src/main/resources/i18n/messages_ru.json index 522df62373..5e0bc74ac2 100644 --- a/src/main/resources/i18n/messages_ru.json +++ b/src/main/resources/i18n/messages_ru.json @@ -1524,10 +1524,8 @@ "validEndDate": "Действительная дата конца", "conceptID": "Идентификатор концепции", "searchData": "Данные поиска", - "relatedConcepts": "Родственное понятие", - "conceptHierarchy": "Иерархия понятий", - "conceptSetData": "Данные ConceptSet", - "conceptData": "Концептуальные данные" + "createdBy": "Автор", + "createdDate": "Дата создания" }, "dataSources": { "headingTitle": "Источники данных", diff --git a/src/main/resources/i18n/messages_zh.json b/src/main/resources/i18n/messages_zh.json index f2ec36318c..47021d4f42 100644 --- a/src/main/resources/i18n/messages_zh.json +++ b/src/main/resources/i18n/messages_zh.json @@ -1524,10 +1524,8 @@ "dpcTooltip": "Descendant Person Count", "conceptID": "概念 ID", "searchData": "搜索数据", - "relatedConcepts":"相关概念", - "conceptHierarchy": "概念层次结构", - "conceptSetData": "概念集数据", - "conceptData": "概念数据" + "createdBy": "由...制作", + "createdDate": "创建日期" }, "dataSources": { "const": { From 7e9359c6669953ea7557e0299a72a1b602e4976f Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Wed, 17 Jul 2024 00:01:56 +0200 Subject: [PATCH 05/19] [ATL-17] Fixed issue with clashing endpoints for conceptset delete operation, moved DB definition for the annotations feature into a single SQL file --- .../ohdsi/webapi/conceptset/ConceptSet.java | 2 +- .../conceptset/ConceptSetGenerationInfo.java | 2 +- .../webapi/conceptset/ConceptSetItem.java | 2 +- .../annotation/ConceptSetAnnotation.java | 2 +- .../webapi/service/ConceptSetService.java | 2 +- ...21100__add_concept_set_meta_data_table.sql | 16 ------- ...conceptset_metadata_module_permissions.sql | 29 ------------ ...nceptset_rename_metadata_to_annotation.sql | 29 ------------ ....0.202407161000__conceptset_annotation.sql | 45 +++++++++++++++++++ 9 files changed, 50 insertions(+), 79 deletions(-) delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java index e19f97ebca..d7498e9e50 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java @@ -37,7 +37,7 @@ * * @author fdefalco */ -@Entity(name = "ConceptSet") +@Entity @Table(name="concept_set") public class ConceptSet extends CommonEntityExt implements Serializable { diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java index 5bc4153a04..e00465388a 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java @@ -21,7 +21,7 @@ * @author Anthony Sena */ -@Entity(name = "ConceptSetGenerationInfo") +@Entity @Table(name = "concept_set_generation_info") @IdClass(ConceptSetGenerationInfoKey.class) public class ConceptSetGenerationInfo implements Serializable { diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java index bd4521bd9c..f329441749 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java @@ -29,7 +29,7 @@ * @author fdefalco */ -@Entity(name = "ConceptSetItem") +@Entity @Table(name="concept_set_item") public class ConceptSetItem implements Serializable{ diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index c6bf9d4819..ad3bc5bb09 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -11,7 +11,7 @@ import javax.persistence.Table; import java.io.Serializable; -@Entity(name = "ConceptSetAnnotation") +@Entity @Table(name = "concept_set_annotation") public class ConceptSetAnnotation extends CommonEntity implements Serializable { /** diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 99780caffd..236284488b 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -939,7 +939,7 @@ public List getConceptSetAnnotation(@PathParam("id") final int id } @DELETE - @Path("/{id}") + @Path("/annotation/{id}") @Produces(MediaType.APPLICATION_JSON) public Response deleteConceptSetAnnotation(@PathParam("id") final int id) { ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository().findById(id); diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql b/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql deleted file mode 100644 index e88371713e..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.2023121100__add_concept_set_meta_data_table.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE SEQUENCE ${ohdsiSchema}.concept_set_meta_data_sequence; - -CREATE TABLE ${ohdsiSchema}.concept_set_meta_data -( - concept_set_meta_data_id int4 NOT NULL DEFAULT nextval('${ohdsiSchema}.concept_set_meta_data_sequence'), - concept_set_id integer NOT NULL, - concept_id integer, - metadata varchar, - created_by_id INTEGER, - created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now()), - modified_by_id INTEGER, - modified_date TIMESTAMP WITH TIME ZONE, - CONSTRAINT pk_concept_set_meta_data_id PRIMARY KEY (concept_set_meta_data_id), - CONSTRAINT fk_concept_set FOREIGN KEY (concept_set_id) - REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) -); diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql deleted file mode 100644 index 51dfb697cc..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202404161000__conceptset_metadata_module_permissions.sql +++ /dev/null @@ -1,29 +0,0 @@ -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:update:*:metadata:put', 'Update Concept Set Metadata'); -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:metadata:put', 'Create Concept Set Metadata'); -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:delete', 'Delete Concept Set Metadata'); -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:metadata:get', 'List Concept Set Metadata'); -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:metadata:get', 'View Concept Set Metadata'); - -INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) -SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id -FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr -WHERE sp.value IN ( - 'conceptset:update:*:metadata:put', - 'conceptset:*:metadata:put', - 'conceptset:%s:delete', - 'conceptset:%s:metadata:get', - 'conceptset:*:metadata:get' - ) AND sr.name IN ('admin'); - -INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) -SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id -FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr -WHERE sp.value IN ( - 'conceptset:%s:metadata:get', - 'conceptset:*:metadata:get' - ) AND sr.name IN ('Atlas users'); \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql deleted file mode 100644 index 2d4178a9e6..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202406281000__conceptset_rename_metadata_to_annotation.sql +++ /dev/null @@ -1,29 +0,0 @@ -ALTER SEQUENCE ${ohdsiSchema}.concept_set_meta_data_sequence RENAME TO concept_set_annotation_sequence; - -ALTER TABLE ${ohdsiSchema}.concept_set_meta_data RENAME TO concept_set_annotation; - -ALTER TABLE ${ohdsiSchema}.concept_set_annotation RENAME COLUMN concept_set_meta_data_id TO concept_set_annotation_id; -ALTER TABLE ${ohdsiSchema}.concept_set_annotation RENAME COLUMN metadata TO annotation_details; - -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:update:*:annotation:put', description = 'Update Concept Set Annotation' -WHERE value = 'conceptset:update:*:metadata:put' AND description = 'Update Concept Set Metadata'; - -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:*:annotation:put', description = 'Create Concept Set Annotation' -WHERE value = 'conceptset:*:metadata:put' AND description = 'Create Concept Set Metadata'; - -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:%s:delete', description = 'Delete Concept Set Annotation' -WHERE value = 'conceptset:%s:delete' AND description = 'Delete Concept Set Metadata'; - -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:%s:annotation:get', description = 'List Concept Set Annotation' -WHERE value = 'conceptset:%s:metadata:get' AND description = 'List Concept Set Metadata'; - -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:*:annotation:get', description = 'View Concept Set Annotation' -WHERE value = 'conceptset:*:metadata:get' AND description = 'View Concept Set Metadata'; - -ALTER TABLE ${ohdsiSchema}.concept_set_annotation -ADD COLUMN vocabulary_version VARCHAR; \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql new file mode 100644 index 0000000000..e0a0922cdb --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql @@ -0,0 +1,45 @@ +CREATE SEQUENCE ${ohdsiSchema}.concept_set_annotation_sequence; + +CREATE TABLE ${ohdsiSchema}.concept_set_annotation +( + concept_set_annotation_id int4 NOT NULL DEFAULT nextval('${ohdsiSchema}.concept_set_annotation_sequence'), + concept_set_id integer NOT NULL, + concept_id integer, + annotation_details VARCHAR, + vocabulary_version VARCHAR, + created_by_id INTEGER, + created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now()), + modified_by_id INTEGER, + modified_date TIMESTAMP WITH TIME ZONE, + CONSTRAINT pk_concept_set_annotation_id PRIMARY KEY (concept_set_annotation_id), + CONSTRAINT fk_concept_set FOREIGN KEY (concept_set_id) + REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) + ON DELETE CASCADE +); + +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:put', 'Create Concept Set Annotation'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:delete', 'Delete Concept Set Annotation'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotation'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:get', 'View Concept Set Annotation'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:*:annotation:put', + 'conceptset:%s:delete', + 'conceptset:%s:annotation:get', + 'conceptset:*:annotation:get' + ) AND sr.name IN ('admin'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:%s:annotation:get', + 'conceptset:*:annotation:get' + ) AND sr.name IN ('Atlas users'); \ No newline at end of file From 25f44ae98f14c60e36e766071e285a07cd19cecc Mon Sep 17 00:00:00 2001 From: Alex Manoylenko Date: Wed, 17 Jul 2024 13:23:23 +0000 Subject: [PATCH 06/19] Correcting newly adjusted DELETE endpoint notation Permitting ATLAS users to create Concept Set Annotations --- .../V2.14.0.202407161000__conceptset_annotation.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql index e0a0922cdb..890336a058 100644 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql @@ -20,7 +20,7 @@ CREATE TABLE ${ohdsiSchema}.concept_set_annotation INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:put', 'Create Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:delete', 'Delete Concept Set Annotation'); + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:delete', 'Delete Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES @@ -31,7 +31,7 @@ SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', - 'conceptset:%s:delete', + 'conceptset:%s:annotation:delete', 'conceptset:%s:annotation:get', 'conceptset:*:annotation:get' ) AND sr.name IN ('admin'); @@ -40,6 +40,7 @@ INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( + 'conceptset:*:annotation:put', 'conceptset:%s:annotation:get', 'conceptset:*:annotation:get' ) AND sr.name IN ('Atlas users'); \ No newline at end of file From 948fde3d33a64bb8180ca575233e9f775065fb0b Mon Sep 17 00:00:00 2001 From: Alex Manoylenko Date: Wed, 17 Jul 2024 13:40:18 +0000 Subject: [PATCH 07/19] Fixing the previous commit as the endpoint is DELETE /conceptset/annotation/{id} --- .../V2.14.0.202407161000__conceptset_annotation.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql index 890336a058..687f68181e 100644 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql @@ -20,7 +20,7 @@ CREATE TABLE ${ohdsiSchema}.concept_set_annotation INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:put', 'Create Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:delete', 'Delete Concept Set Annotation'); + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:annotation:%s:delete', 'Delete Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES @@ -31,7 +31,7 @@ SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', - 'conceptset:%s:annotation:delete', + 'conceptset:annotation:%s:delete', 'conceptset:%s:annotation:get', 'conceptset:*:annotation:get' ) AND sr.name IN ('admin'); From 0fff8582eed2be3e8762b87c4601489f7ef8d98b Mon Sep 17 00:00:00 2001 From: Alex Manoylenko Date: Wed, 17 Jul 2024 13:43:36 +0000 Subject: [PATCH 08/19] Fixing the notation as the endpoint is DELETE /conceptset/annotation/{id} --- .../security/model/ConceptSetAnnotationPermissionSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java index c2eea4141a..bb34b5928c 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java @@ -12,7 +12,7 @@ public class ConceptSetAnnotationPermissionSchema extends EntityPermissionSchema { put("conceptset:*:annotation:put", "Create Concept Set Annotation"); put("conceptset:update:*:annotation:put", "Update Concept Set Annotation"); - put("conceptset:%s:annotation:delete", "Delete Concept Set Annotation with ID %s"); + put("conceptset:annotation:%s:delete", "Delete Concept Set Annotation with ID %s"); } }; From 7adb69bda0876b5474dafcf4db00e64d97de1389 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Fri, 26 Jul 2024 21:55:49 +0200 Subject: [PATCH 09/19] [ATL-58] Added concept set version to annotations --- .../conceptset/annotation/ConceptSetAnnotation.java | 11 +++++++++++ .../org/ohdsi/webapi/service/ConceptSetService.java | 2 ++ .../org/ohdsi/webapi/service/dto/AnnotationDTO.java | 9 +++++++++ ...07261000__add_conceptset_version_to_annotation.sql | 1 + 4 files changed, 23 insertions(+) create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index ad3bc5bb09..df07588218 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -44,6 +44,9 @@ public class ConceptSetAnnotation extends CommonEntity implements Seria @Column(name = "vocabulary_version") private String vocabularyVersion; + @Column(name = "concept_set_version") + private String conceptSetVersion; + public Integer getId() { return id; } @@ -83,4 +86,12 @@ public String getVocabularyVersion() { public void setVocabularyVersion(String vocabularyVersion) { this.vocabularyVersion = vocabularyVersion; } + + public String getConceptSetVersion() { + return conceptSetVersion; + } + + public void setConceptSetVersion(String conceptSetVersion) { + this.conceptSetVersion = conceptSetVersion; + } } diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 236284488b..2c4310dbf0 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -903,6 +903,7 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int id, ConceptSe throw new RuntimeException(e); } conceptSetAnnotation.setVocabularyVersion(m.getVocabularyVersion()); + conceptSetAnnotation.setConceptSetVersion(m.getConceptSetVersion()); conceptSetAnnotation.setConceptId(m.getConceptId()); conceptSetAnnotation.setCreatedBy(getCurrentUser()); conceptSetAnnotation.setCreatedDate(new Date()); @@ -928,6 +929,7 @@ public List getConceptSetAnnotation(@PathParam("id") final int id annotationDTO = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDTO.class); annotationDTO.setId(conceptSetAnnotation.getId()); annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); + annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); annotationDTOList.add(annotationDTO); diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java index ea9c3d2715..6fd27ca1f4 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java @@ -8,6 +8,7 @@ public class AnnotationDTO extends AnnotationDetailsDTO { private String createdBy; private String createdDate; private String vocabularyVersion; + private String conceptSetVersion; public String getCreatedBy() { return createdBy; @@ -32,4 +33,12 @@ public String getVocabularyVersion() { public void setVocabularyVersion(String vocabularyVersion) { this.vocabularyVersion = vocabularyVersion; } + + public String getConceptSetVersion() { + return conceptSetVersion; + } + + public void setConceptSetVersion(String conceptSetVersion) { + this.conceptSetVersion = conceptSetVersion; + } } diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql new file mode 100644 index 0000000000..be7e4ad910 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql @@ -0,0 +1 @@ +ALTER TABLE ${ohdsiSchema}.concept_set_annotation ADD concept_set_version VARCHAR; \ No newline at end of file From f40a666f76e8854e28f2af95cdd812b4b4e44175 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Tue, 30 Jul 2024 13:19:32 +0200 Subject: [PATCH 10/19] [ATL-58] Implemented copying of annotations along with ConceptSet --- .../webapi/service/ConceptSetService.java | 27 +++++++++++++++++++ .../service/dto/CopyAnnotationsRequest.java | 23 ++++++++++++++++ ...407301000__copy_annotations_permission.sql | 20 ++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/main/java/org/ohdsi/webapi/service/dto/CopyAnnotationsRequest.java create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 2c4310dbf0..df7bce65f0 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -47,6 +47,7 @@ import org.ohdsi.webapi.service.dto.ConceptSetDTO; import org.ohdsi.webapi.service.dto.ConceptSetAnnotationDTO; import org.ohdsi.webapi.service.dto.AnnotationDTO; +import org.ohdsi.webapi.service.dto.CopyAnnotationsRequest; import org.ohdsi.webapi.shiro.Entities.UserEntity; import org.ohdsi.webapi.shiro.Entities.UserRepository; import org.ohdsi.webapi.shiro.management.Security; @@ -916,6 +917,32 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int id, ConceptSe return true; } + private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSetAnnotation, int targetConceptSetId){ + ConceptSetAnnotation targetConceptSetAnnotation = new ConceptSetAnnotation(); + targetConceptSetAnnotation.setConceptSetId(targetConceptSetId); + targetConceptSetAnnotation.setConceptSetVersion(sourceConceptSetAnnotation.getConceptSetVersion()); + targetConceptSetAnnotation.setAnnotationDetails(sourceConceptSetAnnotation.getAnnotationDetails()); + targetConceptSetAnnotation.setConceptId(sourceConceptSetAnnotation.getConceptId()); + targetConceptSetAnnotation.setVocabularyVersion(sourceConceptSetAnnotation.getVocabularyVersion()); + targetConceptSetAnnotation.setCreatedBy(sourceConceptSetAnnotation.getCreatedBy()); + targetConceptSetAnnotation.setCreatedDate(sourceConceptSetAnnotation.getCreatedDate()); + targetConceptSetAnnotation.setModifiedBy(sourceConceptSetAnnotation.getModifiedBy()); + targetConceptSetAnnotation.setModifiedDate(sourceConceptSetAnnotation.getModifiedDate()); + return targetConceptSetAnnotation; + } + + @POST + @Path("/copy-annotations") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { + List sourceAnnotations = getConceptSetAnnotationRepository().findByConceptSetId(copyAnnotationsRequest.getSourceConceptSetId()); + List copiedAnnotations= sourceAnnotations.stream() + .map(sourceAnnotation -> copyAnnotation(sourceAnnotation, copyAnnotationsRequest.getTargetConceptSetId())) + .collect(Collectors.toList()); + getConceptSetAnnotationRepository().save(copiedAnnotations); + } + @GET @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/ohdsi/webapi/service/dto/CopyAnnotationsRequest.java b/src/main/java/org/ohdsi/webapi/service/dto/CopyAnnotationsRequest.java new file mode 100644 index 0000000000..1d9313b5e9 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/dto/CopyAnnotationsRequest.java @@ -0,0 +1,23 @@ +package org.ohdsi.webapi.service.dto; + +public class CopyAnnotationsRequest { + + private int sourceConceptSetId; + private int targetConceptSetId; + + public int getSourceConceptSetId() { + return sourceConceptSetId; + } + + public int getTargetConceptSetId() { + return targetConceptSetId; + } + + public void setSourceConceptSetId(int sourceConceptSetId) { + this.sourceConceptSetId = sourceConceptSetId; + } + + public void setTargetConceptSetId(int targetConceptSetId) { + this.targetConceptSetId = targetConceptSetId; + } +} diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql new file mode 100644 index 0000000000..ba5949dd94 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql @@ -0,0 +1,20 @@ + +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:copy-annotations', 'Copy Concept Set Annotations'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:copy-annotations' + ) AND sr.name IN ('admin'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ( + 'conceptset:copy-annotations' + ) AND sr.name IN ('Atlas users'); + +UPDATE ${ohdsiSchema}.sec_permission SET value='conceptset:annotation:*:delete' +WHERE value='conceptset:annotation:%s:delete'; \ No newline at end of file From 2b9c35a6193397b2247a8c0c7ee80c4115c7d486 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Tue, 6 Aug 2024 16:53:40 +0200 Subject: [PATCH 11/19] [ATL-58] Fixed permissions issues for annotations feature, transformed search data JSON to a human friendly format in annotations tab --- .../ConceptSetAnnotationPermissionSchema.java | 5 - .../webapi/service/ConceptSetService.java | 49 ++++---- .../annotations/SearchDataTransformer.java | 105 ++++++++++++++++++ ... => SaveConceptSetAnnotationsRequest.java} | 2 +- ...408061000__fix_annotations_permissions.sql | 3 + .../SearchDataTransformerTest.java | 79 +++++++++++++ 6 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java rename src/main/java/org/ohdsi/webapi/service/dto/{ConceptSetAnnotationDTO.java => SaveConceptSetAnnotationsRequest.java} (92%) create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql create mode 100644 src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java index bb34b5928c..e75414df8d 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java @@ -10,21 +10,16 @@ public class ConceptSetAnnotationPermissionSchema extends EntityPermissionSchema private static Map writePermissions = new HashMap() { { - put("conceptset:*:annotation:put", "Create Concept Set Annotation"); - put("conceptset:update:*:annotation:put", "Update Concept Set Annotation"); put("conceptset:annotation:%s:delete", "Delete Concept Set Annotation with ID %s"); } }; private static Map readPermissions = new HashMap() { { - put("conceptset:%s:annotation:get", "List Concept Set Annotation with id %s"); - put("conceptset:*:annotation:get", "View Concept Set Annotation expression"); } }; public ConceptSetAnnotationPermissionSchema() { - super(EntityType.CONCEPT_SET_ANNOTATION, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index df7bce65f0..be9955c0d8 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; import org.apache.shiro.authz.UnauthorizedException; import org.ohdsi.circe.vocabulary.ConceptSetExpression; import org.ohdsi.vocabulary.Concept; @@ -43,9 +44,10 @@ import org.ohdsi.webapi.conceptset.annotation.ConceptSetAnnotation; import org.ohdsi.webapi.exception.ConceptNotExistException; import org.ohdsi.webapi.security.PermissionService; +import org.ohdsi.webapi.service.annotations.SearchDataTransformer; import org.ohdsi.webapi.service.dto.AnnotationDetailsDTO; import org.ohdsi.webapi.service.dto.ConceptSetDTO; -import org.ohdsi.webapi.service.dto.ConceptSetAnnotationDTO; +import org.ohdsi.webapi.service.dto.SaveConceptSetAnnotationsRequest; import org.ohdsi.webapi.service.dto.AnnotationDTO; import org.ohdsi.webapi.service.dto.CopyAnnotationsRequest; import org.ohdsi.webapi.shiro.Entities.UserEntity; @@ -114,6 +116,12 @@ public class ConceptSetService extends AbstractDaoService implements HasTags versionService; + @Autowired + private SearchDataTransformer searchDataTransformer; + + @Autowired + private ObjectMapper mapper; + @Value("${security.defaultGlobalReadPermissions}") private boolean defaultGlobalReadPermissions; @@ -875,8 +883,8 @@ private ConceptSetVersion saveVersion(int id) { * The body has two parts: 1) the elements new concept which added to the * concept set. 2) the elements concept which remove from concept set. * - * @param id The concept set ID - * @param dto An object of 2 Array new annotation and remove annotation + * @param conceptSetId The concept set ID + * @param request An object of 2 Array new annotation and remove annotation * @return Boolean: true if the save is successful * @summary Create new or delete concept set annotation items */ @@ -884,19 +892,12 @@ private ConceptSetVersion saveVersion(int id) { @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) @Transactional - public boolean saveConceptSetAnnotation(@PathParam("id") final int id, ConceptSetAnnotationDTO dto) { - if (dto.getRemoveAnnotation() != null && !dto.getRemoveAnnotation().isEmpty()) { - for (AnnotationDTO annotationDTO : dto.getRemoveAnnotation()) { - this.getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndConceptId(id, annotationDTO.getConceptId()); - } -// getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndInConceptId(id, -// dto.getRemoveAnnotation().stream().map(AnnotationDTO::getConceptId).collect(Collectors.toList())); - } - ObjectMapper mapper = new ObjectMapper(); - if (dto.getNewAnnotation() != null && !dto.getNewAnnotation().isEmpty()) { - List annotationList = dto.getNewAnnotation().stream().map(m -> { + public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, SaveConceptSetAnnotationsRequest request) { + removeAnnotations(conceptSetId, request); + if (CollectionUtils.isNotEmpty(request.getNewAnnotation())) { + List annotationList = request.getNewAnnotation().stream().map(m -> { ConceptSetAnnotation conceptSetAnnotation = new ConceptSetAnnotation(); - conceptSetAnnotation.setConceptSetId(id); + conceptSetAnnotation.setConceptSetId(conceptSetId); try { AnnotationDetailsDTO annotationDetailsDTO = mapper.readValue(mapper.writeValueAsString(m), AnnotationDetailsDTO.class); conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDetailsDTO)); @@ -916,7 +917,13 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int id, ConceptSe return true; } - + private void removeAnnotations(int id, SaveConceptSetAnnotationsRequest request){ + if (CollectionUtils.isNotEmpty(request.getRemoveAnnotation())) { + for (AnnotationDTO annotationDTO : request.getRemoveAnnotation()) { + this.getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndConceptId(id, annotationDTO.getConceptId()); + } + } + } private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSetAnnotation, int targetConceptSetId){ ConceptSetAnnotation targetConceptSetAnnotation = new ConceptSetAnnotation(); targetConceptSetAnnotation.setConceptSetId(targetConceptSetId); @@ -947,11 +954,10 @@ public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) public List getConceptSetAnnotation(@PathParam("id") final int id) { - ObjectMapper mapper = new ObjectMapper(); List annotationList = getConceptSetAnnotationRepository().findByConceptSetId(id); List annotationDTOList = new ArrayList<>(); for (ConceptSetAnnotation conceptSetAnnotation : annotationList) { - AnnotationDTO annotationDTO = null; + AnnotationDTO annotationDTO; try { annotationDTO = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDTO.class); annotationDTO.setId(conceptSetAnnotation.getId()); @@ -959,7 +965,11 @@ public List getConceptSetAnnotation(@PathParam("id") final int id annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); - annotationDTOList.add(annotationDTO); + + String searchDataJSON = annotationDTO.getSearchData(); + String humanReadableData = searchDataTransformer.convertJsonToReadableFormat(searchDataJSON); + annotationDTO.setSearchData(humanReadableData); + annotationDTOList.add(annotationDTO); } catch (IOException e) { throw new RuntimeException(e); } @@ -982,7 +992,6 @@ public Response deleteConceptSetAnnotation(@PathParam("id") final int id) { @Path("/update/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) public AnnotationDTO updateConceptSetAnnotation(@PathParam("id") final int id, AnnotationDTO annotationDTO) throws IOException { - ObjectMapper mapper = new ObjectMapper(); ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository() .findConceptSetAnnotationByConceptIdAndConceptId(id, annotationDTO.getConceptId()) .orElseThrow(() -> new RuntimeException("Concept set annotation not found")); diff --git a/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java b/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java new file mode 100644 index 0000000000..309b3253c3 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java @@ -0,0 +1,105 @@ +package org.ohdsi.webapi.service.annotations; + +import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.stereotype.Service; + +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@Service +public class SearchDataTransformer { + + private static final String FILTER_DATA = "filterData"; + private static final String TITLE = "title"; + private static final String VALUE = "value"; + private static final String FILTER_SOURCE = "filterSource"; + private static final String FILTER_SOURCE_LABEL = "Filter Source"; + private static final String SEARCH_TEXT = "searchText"; + private static final String SEARCH_TEXT_LABEL = "Search Text"; + private static final String FILTER_COLUMNS = "filterColumns"; + private static final String QUOTE = "\""; + + private static final String DELIMITER = ", "; + + private static final String ENTRY_FORMAT = "%s: \"%s\""; + + public String convertJsonToReadableFormat(String jsonInput) { + JSONObject searchObject = new JSONObject(jsonInput); + StringBuilder result = new StringBuilder(); + + String filterDataResult = extractFilterData(searchObject); + appendCommaSeparated(result, filterDataResult); + + String filterSourceResult = processFilterSource(searchObject); + appendCommaSeparated(result, filterSourceResult); + + return result.toString(); + } + + private String extractFilterData(JSONObject jsonObject) { + JSONObject filterData = jsonObject.optJSONObject(FILTER_DATA); + if (filterData != null) { + String searchText = extractSearchText(filterData); + String filterColumns = extractFilterColumns(filterData); + return Stream.of(searchText, filterColumns) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.joining(DELIMITER)); + } + JSONArray filterDataArray = jsonObject.optJSONArray(FILTER_DATA); + if (filterDataArray != null) { + return formatKeyValuePairs(filterDataArray); + } + return ""; + } + + private String extractFilterColumns(JSONObject filterData) { + JSONArray filterColumns = filterData.optJSONArray(FILTER_COLUMNS); + if (filterColumns != null) { + return formatKeyValuePairs(filterColumns); + } + return ""; + } + + private String processFilterSource(JSONObject jsonObject) { + String filterSource = jsonObject.optString(FILTER_SOURCE, ""); + if (!filterSource.isEmpty()) { + return String.format(ENTRY_FORMAT, FILTER_SOURCE_LABEL, filterSource); + } + return ""; + } + + private String extractSearchText(JSONObject jsonObject) { + String searchText = jsonObject.optString(SEARCH_TEXT, ""); + if (!searchText.isEmpty()) { + return String.format(ENTRY_FORMAT, SEARCH_TEXT_LABEL, searchText); + } + return ""; + } + + private String formatKeyValuePairs(JSONArray filterColumns) { + return IntStream.range(0, filterColumns.length()) + .mapToObj(index -> { + JSONObject item = filterColumns.getJSONObject(index); + String title = optString(item, TITLE); + String value = StringUtils.unwrap(optString(item, VALUE), QUOTE); + return String.format(ENTRY_FORMAT, title, value); + }) + .collect(Collectors.joining(DELIMITER)); + } + + private void appendCommaSeparated(StringBuilder resultBuilder, String part) { + if (!part.isEmpty()) { + if (resultBuilder.length() > 0) { + resultBuilder.append(DELIMITER); + } + resultBuilder.append(part); + } + } + + private String optString(JSONObject item, String key) { + return item.optString(key, ""); + } +} diff --git a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/SaveConceptSetAnnotationsRequest.java similarity index 92% rename from src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java rename to src/main/java/org/ohdsi/webapi/service/dto/SaveConceptSetAnnotationsRequest.java index 410f25ccb4..3d8469ab99 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/ConceptSetAnnotationDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/SaveConceptSetAnnotationsRequest.java @@ -2,7 +2,7 @@ import java.util.List; -public class ConceptSetAnnotationDTO { +public class SaveConceptSetAnnotationsRequest { private List newAnnotation; diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql new file mode 100644 index 0000000000..9e598539bb --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql @@ -0,0 +1,3 @@ +UPDATE ${ohdsiSchema}.sec_permission +SET value = 'conceptset:annotation:*:delete', description = 'Delete Concept Set Annotation' +WHERE value = 'conceptset:annotation:%s:delete'; \ No newline at end of file diff --git a/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java b/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java new file mode 100644 index 0000000000..104450f3c8 --- /dev/null +++ b/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java @@ -0,0 +1,79 @@ +package org.ohdsi.webapi.service.annotations; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.not; + +public class SearchDataTransformerTest { + + private SearchDataTransformer sut; + + @Before + public void setUp() { + sut = new SearchDataTransformer(); + } + + @Test + public void shouldReturnEmptyStringWhenInputIsEmpty() { + JSONObject emptyJson = new JSONObject(); + String transformed = sut.convertJsonToReadableFormat(emptyJson.toString()); + assertThat(transformed, isEmptyString()); + } + + @Test + public void shouldHandleSearchText() { + String input = "{\"filterData\":{\"searchText\":\"testSearch\"}}"; + String result = sut.convertJsonToReadableFormat(input); + assertThat(result, is("Search Text: \"testSearch\"")); + } + + @Test + public void shouldHandleFilterSource() { + String input = "{\"filterSource\":\"Search\"}"; + String result = sut.convertJsonToReadableFormat(input); + assertThat(result, is("Filter Source: \"Search\"")); + } + + @Test + public void shouldHandleFilterColumns() { + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"}]} }"; + String result = sut.convertJsonToReadableFormat(input); + assertThat(result, is("Domain: \"Drug\"")); + } + + @Test + public void shouldCombineFilterDataAndFilterSource() { + String input = "{\"filterData\":{\"searchText\":\"testSearch\",\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"}]},\"filterSource\":\"Search\"}"; + String result = sut.convertJsonToReadableFormat(input); + String expected = "Search Text: \"testSearch\", Domain: \"Drug\", Filter Source: \"Search\""; + assertThat(result, is(expected)); + } + + @Test + public void shouldHandleMultipleFilterColumns() { + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"},{\"title\":\"Class\",\"value\":\"Medication\"}]}}"; + String result = sut.convertJsonToReadableFormat(input); + String expected = "Domain: \"Drug\", Class: \"Medication\""; + assertThat(result, is(expected)); + } + + @Test + public void shouldIgnoreEmptyFilterColumnsAndSearchText() { + String input = "{\"filterData\":{\"searchText\":\"\",\"filterColumns\":[]}, \"filterSource\":\"\"}"; + String result = sut.convertJsonToReadableFormat(input); + assertThat(result, isEmptyString()); + } + + @Test + public void shouldHandleNullValuesGracefully() { + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":null,\"value\":null}], \"searchText\":null}, \"filterSource\":null}"; + String result = sut.convertJsonToReadableFormat(input); + assertThat(result, not(containsString("null"))); + } +} \ No newline at end of file From 485b41ad4e138608dd59f78c75385b26a824715c Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Thu, 22 Aug 2024 20:09:25 +0200 Subject: [PATCH 12/19] Fix for annotation search data shown as JSON instead of human readable format --- .../webapi/service/ConceptSetService.java | 62 ++++++++++++------- .../webapi/service/dto/AnnotationDTO.java | 29 ++++++++- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index be9955c0d8..eecf985ffc 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -895,18 +895,23 @@ private ConceptSetVersion saveVersion(int id) { public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, SaveConceptSetAnnotationsRequest request) { removeAnnotations(conceptSetId, request); if (CollectionUtils.isNotEmpty(request.getNewAnnotation())) { - List annotationList = request.getNewAnnotation().stream().map(m -> { + List annotationList = request.getNewAnnotation() + .stream() + .map(newAnnotationData -> { ConceptSetAnnotation conceptSetAnnotation = new ConceptSetAnnotation(); conceptSetAnnotation.setConceptSetId(conceptSetId); try { - AnnotationDetailsDTO annotationDetailsDTO = mapper.readValue(mapper.writeValueAsString(m), AnnotationDetailsDTO.class); + AnnotationDetailsDTO annotationDetailsDTO = new AnnotationDetailsDTO(); + annotationDetailsDTO.setId(newAnnotationData.getId()); + annotationDetailsDTO.setConceptId(newAnnotationData.getConceptId()); + annotationDetailsDTO.setSearchData(newAnnotationData.getSearchData()); conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDetailsDTO)); } catch (JsonProcessingException e) { throw new RuntimeException(e); } - conceptSetAnnotation.setVocabularyVersion(m.getVocabularyVersion()); - conceptSetAnnotation.setConceptSetVersion(m.getConceptSetVersion()); - conceptSetAnnotation.setConceptId(m.getConceptId()); + conceptSetAnnotation.setVocabularyVersion(newAnnotationData.getVocabularyVersion()); + conceptSetAnnotation.setConceptSetVersion(newAnnotationData.getConceptSetVersion()); + conceptSetAnnotation.setConceptId(newAnnotationData.getConceptId()); conceptSetAnnotation.setCreatedBy(getCurrentUser()); conceptSetAnnotation.setCreatedDate(new Date()); return conceptSetAnnotation; @@ -955,26 +960,35 @@ public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { @Produces(MediaType.APPLICATION_JSON) public List getConceptSetAnnotation(@PathParam("id") final int id) { List annotationList = getConceptSetAnnotationRepository().findByConceptSetId(id); - List annotationDTOList = new ArrayList<>(); - for (ConceptSetAnnotation conceptSetAnnotation : annotationList) { - AnnotationDTO annotationDTO; - try { - annotationDTO = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDTO.class); - annotationDTO.setId(conceptSetAnnotation.getId()); - annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); - annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); - annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); - annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); - - String searchDataJSON = annotationDTO.getSearchData(); - String humanReadableData = searchDataTransformer.convertJsonToReadableFormat(searchDataJSON); - annotationDTO.setSearchData(humanReadableData); - annotationDTOList.add(annotationDTO); - } catch (IOException e) { - throw new RuntimeException(e); - } + return annotationList.stream() + .map(this::convertAnnotationEntityToDTO) + .collect(Collectors.toList()); + } + + + private AnnotationDTO convertAnnotationEntityToDTO(ConceptSetAnnotation conceptSetAnnotation) { + AnnotationDetailsDTO annotationDetails; + try { + annotationDetails = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDetailsDTO.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); } - return annotationDTOList; + + AnnotationDTO annotationDTO = new AnnotationDTO(); + + annotationDTO.setId(conceptSetAnnotation.getId()); + annotationDTO.setConceptId(conceptSetAnnotation.getConceptId()); + + String searchDataJSON = annotationDetails.getSearchData(); + String humanReadableData = searchDataTransformer.convertJsonToReadableFormat(searchDataJSON); + annotationDTO.setSearchData(humanReadableData); + + annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); + annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); + annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); + annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); + + return annotationDTO; } @DELETE diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java index 6fd27ca1f4..b8a24f8485 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java @@ -3,12 +3,15 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) -public class AnnotationDTO extends AnnotationDetailsDTO { +public class AnnotationDTO { + private Integer id; private String createdBy; private String createdDate; private String vocabularyVersion; private String conceptSetVersion; + private String searchData; + private Integer conceptId; public String getCreatedBy() { return createdBy; @@ -41,4 +44,28 @@ public String getConceptSetVersion() { public void setConceptSetVersion(String conceptSetVersion) { this.conceptSetVersion = conceptSetVersion; } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSearchData() { + return searchData; + } + + public void setSearchData(String searchData) { + this.searchData = searchData; + } + + public Integer getConceptId() { + return conceptId; + } + + public void setConceptId(Integer conceptId) { + this.conceptId = conceptId; + } } From 937c72e8851b81e9e42c7e5920daa109eb4225eb Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Thu, 12 Sep 2024 10:56:50 +0200 Subject: [PATCH 13/19] Added 'Copied From' list of concept set ids for concept set annotations --- .../annotation/ConceptSetAnnotation.java | 11 +++++++++++ .../ohdsi/webapi/service/ConceptSetService.java | 14 ++++++++++++-- .../ohdsi/webapi/service/dto/AnnotationDTO.java | 9 +++++++++ ...000__add_copied_from_concept_set_annotation.sql | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index df07588218..24a0b15cfa 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -47,6 +47,9 @@ public class ConceptSetAnnotation extends CommonEntity implements Seria @Column(name = "concept_set_version") private String conceptSetVersion; + @Column(name = "copied_from_concept_set_ids") + private String copiedFromConceptSetIds; + public Integer getId() { return id; } @@ -94,4 +97,12 @@ public String getConceptSetVersion() { public void setConceptSetVersion(String conceptSetVersion) { this.conceptSetVersion = conceptSetVersion; } + + public String getCopiedFromConceptSetIds() { + return copiedFromConceptSetIds; + } + + public void setCopiedFromConceptSetIds(String copiedFromConceptSetIds) { + this.copiedFromConceptSetIds = copiedFromConceptSetIds; + } } diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index eecf985ffc..a606928f6d 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.UnauthorizedException; import org.ohdsi.circe.vocabulary.ConceptSetExpression; import org.ohdsi.vocabulary.Concept; @@ -929,7 +930,7 @@ private void removeAnnotations(int id, SaveConceptSetAnnotationsRequest request) } } } - private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSetAnnotation, int targetConceptSetId){ + private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSetAnnotation, int sourceConceptSetId, int targetConceptSetId){ ConceptSetAnnotation targetConceptSetAnnotation = new ConceptSetAnnotation(); targetConceptSetAnnotation.setConceptSetId(targetConceptSetId); targetConceptSetAnnotation.setConceptSetVersion(sourceConceptSetAnnotation.getConceptSetVersion()); @@ -940,9 +941,17 @@ private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSe targetConceptSetAnnotation.setCreatedDate(sourceConceptSetAnnotation.getCreatedDate()); targetConceptSetAnnotation.setModifiedBy(sourceConceptSetAnnotation.getModifiedBy()); targetConceptSetAnnotation.setModifiedDate(sourceConceptSetAnnotation.getModifiedDate()); + targetConceptSetAnnotation.setCopiedFromConceptSetIds(appendCopiedFromConceptSetId(sourceConceptSetAnnotation.getCopiedFromConceptSetIds(), sourceConceptSetId)); return targetConceptSetAnnotation; } + private String appendCopiedFromConceptSetId(String copiedFromConceptSetIds, int sourceConceptSetId) { + if(StringUtils.isEmpty(copiedFromConceptSetIds)){ + return Integer.toString(sourceConceptSetId); + } + return copiedFromConceptSetIds.concat(",").concat(Integer.toString(sourceConceptSetId)); + } + @POST @Path("/copy-annotations") @Produces(MediaType.APPLICATION_JSON) @@ -950,7 +959,7 @@ private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSe public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { List sourceAnnotations = getConceptSetAnnotationRepository().findByConceptSetId(copyAnnotationsRequest.getSourceConceptSetId()); List copiedAnnotations= sourceAnnotations.stream() - .map(sourceAnnotation -> copyAnnotation(sourceAnnotation, copyAnnotationsRequest.getTargetConceptSetId())) + .map(sourceAnnotation -> copyAnnotation(sourceAnnotation, copyAnnotationsRequest.getSourceConceptSetId(), copyAnnotationsRequest.getTargetConceptSetId())) .collect(Collectors.toList()); getConceptSetAnnotationRepository().save(copiedAnnotations); } @@ -985,6 +994,7 @@ private AnnotationDTO convertAnnotationEntityToDTO(ConceptSetAnnotation conceptS annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); + annotationDTO.setCopiedFromConceptSetIds(conceptSetAnnotation.getCopiedFromConceptSetIds()); annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java index b8a24f8485..bf1e2ac700 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java @@ -11,6 +11,7 @@ public class AnnotationDTO { private String vocabularyVersion; private String conceptSetVersion; private String searchData; + private String copiedFromConceptSetIds; private Integer conceptId; public String getCreatedBy() { @@ -68,4 +69,12 @@ public Integer getConceptId() { public void setConceptId(Integer conceptId) { this.conceptId = conceptId; } + + public String getCopiedFromConceptSetIds() { + return copiedFromConceptSetIds; + } + + public void setCopiedFromConceptSetIds(String copiedFromConceptSetIds) { + this.copiedFromConceptSetIds = copiedFromConceptSetIds; + } } diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql new file mode 100644 index 0000000000..9e168407f6 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql @@ -0,0 +1 @@ +ALTER TABLE ${ohdsiSchema}.concept_set_annotation ADD copied_from_concept_set_ids VARCHAR; \ No newline at end of file From 6a88e5da81fdb82ff26f37495b32d389d8c4b305 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Wed, 18 Sep 2024 13:11:22 +0200 Subject: [PATCH 14/19] Added i18n records for Origin Concept Sets ('Copied From' feature in annotations) --- src/main/resources/i18n/messages_en.json | 1 + src/main/resources/i18n/messages_ko.json | 1 + src/main/resources/i18n/messages_ru.json | 1 + src/main/resources/i18n/messages_zh.json | 1 + 4 files changed, 4 insertions(+) diff --git a/src/main/resources/i18n/messages_en.json b/src/main/resources/i18n/messages_en.json index f4642dcd47..19fba9756e 100644 --- a/src/main/resources/i18n/messages_en.json +++ b/src/main/resources/i18n/messages_en.json @@ -1408,6 +1408,7 @@ "notPrevalent": "Not Prevalent", "optimizedOut": "Optimized Out", "options": "Options", + "originConceptSets": "Origin Concept Sets", "outcomeCohortName": "Outcome Cohort Name", "outcomeId": "Outcome Id", "outcomeModel": "Outcome Model", diff --git a/src/main/resources/i18n/messages_ko.json b/src/main/resources/i18n/messages_ko.json index 96775b83f0..c97f79854f 100644 --- a/src/main/resources/i18n/messages_ko.json +++ b/src/main/resources/i18n/messages_ko.json @@ -1408,6 +1408,7 @@ "notPrevalent": "만연하지 않음(Not Prevalent)", "optimizedOut": "최적화", "options": "옵션", + "originConceptSets": "오리진 컨셉트 세트", "outcomeCohortName": "아웃컴(outcome) 코호트 이름", "outcomeId": "아웃컴(outcome) ID", "outcomeModel": "결과 모델", diff --git a/src/main/resources/i18n/messages_ru.json b/src/main/resources/i18n/messages_ru.json index 5e0bc74ac2..1cd36105ca 100644 --- a/src/main/resources/i18n/messages_ru.json +++ b/src/main/resources/i18n/messages_ru.json @@ -1338,6 +1338,7 @@ "binary": "Двоичный", "model": "Модель", "options": "Опции", + "originConceptSets": "Исходные наборы концепций", "firstExposureOnly": "Только первое знакомство", "washoutPeriod": "Период вымывания", "includeAllOutcomes": "Включить все результаты", diff --git a/src/main/resources/i18n/messages_zh.json b/src/main/resources/i18n/messages_zh.json index 47021d4f42..87a5183ae0 100644 --- a/src/main/resources/i18n/messages_zh.json +++ b/src/main/resources/i18n/messages_zh.json @@ -1408,6 +1408,7 @@ "notPrevalent": "不普遍", "optimizedOut": "优化了", "options": "选择", + "originConceptSets": "起源概念集", "outcomeCohortName": "结果队列名称", "outcomeId": "结果编号", "outcomeModel": "结果模型", From deed559d3bdcc05d144508044df175a1ad8caf10 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Wed, 6 Nov 2024 08:24:28 +0100 Subject: [PATCH 15/19] A combined migration script has been assembled Reverting back entity names in @Entity according to the codebase policy --- .../ohdsi/webapi/conceptset/ConceptSet.java | 2 +- .../conceptset/ConceptSetGenerationInfo.java | 2 +- .../webapi/conceptset/ConceptSetItem.java | 2 +- .../annotation/ConceptSetAnnotation.java | 2 +- ...__add_conceptset_version_to_annotation.sql | 1 - ...407301000__copy_annotations_permission.sql | 20 ------------------- ...408061000__fix_annotations_permissions.sql | 3 --- ...add_copied_from_concept_set_annotation.sql | 1 - ...0240716100000__conceptset_annotations.sql} | 15 ++++++++++---- 9 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql delete mode 100644 src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql rename src/main/resources/db/migration/postgresql/{V2.14.0.202407161000__conceptset_annotation.sql => V2.15.0.20240716100000__conceptset_annotations.sql} (80%) diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java index d7498e9e50..fb360e0d85 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java @@ -37,7 +37,7 @@ * * @author fdefalco */ -@Entity +@Entity(name="ConceptSet") @Table(name="concept_set") public class ConceptSet extends CommonEntityExt implements Serializable { diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java index e00465388a..5bc4153a04 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetGenerationInfo.java @@ -21,7 +21,7 @@ * @author Anthony Sena */ -@Entity +@Entity(name = "ConceptSetGenerationInfo") @Table(name = "concept_set_generation_info") @IdClass(ConceptSetGenerationInfoKey.class) public class ConceptSetGenerationInfo implements Serializable { diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java index f329441749..bd4521bd9c 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSetItem.java @@ -29,7 +29,7 @@ * @author fdefalco */ -@Entity +@Entity(name = "ConceptSetItem") @Table(name="concept_set_item") public class ConceptSetItem implements Serializable{ diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index 24a0b15cfa..d12741dc3e 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -11,7 +11,7 @@ import javax.persistence.Table; import java.io.Serializable; -@Entity +@Entity(name = "ConceptSetAnnotation") @Table(name = "concept_set_annotation") public class ConceptSetAnnotation extends CommonEntity implements Serializable { /** diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql deleted file mode 100644 index be7e4ad910..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202407261000__add_conceptset_version_to_annotation.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE ${ohdsiSchema}.concept_set_annotation ADD concept_set_version VARCHAR; \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql deleted file mode 100644 index ba5949dd94..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202407301000__copy_annotations_permission.sql +++ /dev/null @@ -1,20 +0,0 @@ - -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:copy-annotations', 'Copy Concept Set Annotations'); - -INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) -SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id -FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr -WHERE sp.value IN ( - 'conceptset:copy-annotations' - ) AND sr.name IN ('admin'); - -INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) -SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id -FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr -WHERE sp.value IN ( - 'conceptset:copy-annotations' - ) AND sr.name IN ('Atlas users'); - -UPDATE ${ohdsiSchema}.sec_permission SET value='conceptset:annotation:*:delete' -WHERE value='conceptset:annotation:%s:delete'; \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql b/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql deleted file mode 100644 index 9e598539bb..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202408061000__fix_annotations_permissions.sql +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE ${ohdsiSchema}.sec_permission -SET value = 'conceptset:annotation:*:delete', description = 'Delete Concept Set Annotation' -WHERE value = 'conceptset:annotation:%s:delete'; \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql b/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql deleted file mode 100644 index 9e168407f6..0000000000 --- a/src/main/resources/db/migration/postgresql/V2.14.0.20240912000000__add_copied_from_concept_set_annotation.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE ${ohdsiSchema}.concept_set_annotation ADD copied_from_concept_set_ids VARCHAR; \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql b/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql similarity index 80% rename from src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql rename to src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql index 687f68181e..fd39cece3e 100644 --- a/src/main/resources/db/migration/postgresql/V2.14.0.202407161000__conceptset_annotation.sql +++ b/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql @@ -11,6 +11,8 @@ CREATE TABLE ${ohdsiSchema}.concept_set_annotation created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (now()), modified_by_id INTEGER, modified_date TIMESTAMP WITH TIME ZONE, + concept_set_version VARCHAR(100), + copied_from_concept_set_ids VARCHAR(1000), CONSTRAINT pk_concept_set_annotation_id PRIMARY KEY (concept_set_annotation_id), CONSTRAINT fk_concept_set FOREIGN KEY (concept_set_id) REFERENCES ${ohdsiSchema}.concept_set (concept_set_id) @@ -20,20 +22,24 @@ CREATE TABLE ${ohdsiSchema}.concept_set_annotation INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:put', 'Create Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:annotation:%s:delete', 'Delete Concept Set Annotation'); + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:annotation:*:delete', 'Delete Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:get', 'View Concept Set Annotation'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:copy-annotations', 'Copy Concept Set Annotations'); + INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', - 'conceptset:annotation:%s:delete', + 'conceptset:annotation:*:delete', 'conceptset:%s:annotation:get', - 'conceptset:*:annotation:get' + 'conceptset:*:annotation:get', + 'conceptset:copy-annotations' ) AND sr.name IN ('admin'); INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) @@ -42,5 +48,6 @@ FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', 'conceptset:%s:annotation:get', - 'conceptset:*:annotation:get' + 'conceptset:*:annotation:get', + 'conceptset:copy-annotations' ) AND sr.name IN ('Atlas users'); \ No newline at end of file From 1f6476c2091004c21d144e1490a22dad0cc63723 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Sat, 9 Nov 2024 01:21:29 +0100 Subject: [PATCH 16/19] Changed owner/permissions check for annotations delete to consider ownership of the concept set for annotations --- .../annotation/ConceptSetAnnotation.java | 2 +- .../ConceptSetAnnotationPermissionSchema.java | 25 ------------------- .../model/ConceptSetPermissionSchema.java | 5 +++- .../webapi/security/model/EntityType.java | 5 +--- .../webapi/service/ConceptSetService.java | 19 +++----------- 5 files changed, 10 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index d12741dc3e..df70466c50 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -13,7 +13,7 @@ @Entity(name = "ConceptSetAnnotation") @Table(name = "concept_set_annotation") -public class ConceptSetAnnotation extends CommonEntity implements Serializable { +public class ConceptSetAnnotation implements Serializable { /** * */ diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java deleted file mode 100644 index e75414df8d..0000000000 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetAnnotationPermissionSchema.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.ohdsi.webapi.security.model; - -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -@Component -public class ConceptSetAnnotationPermissionSchema extends EntityPermissionSchema { - - private static Map writePermissions = new HashMap() { - { - put("conceptset:annotation:%s:delete", "Delete Concept Set Annotation with ID %s"); - } - }; - - private static Map readPermissions = new HashMap() { - { - } - }; - - public ConceptSetAnnotationPermissionSchema() { - super(EntityType.CONCEPT_SET_ANNOTATION, readPermissions, writePermissions); - } -} diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java index 66b4b1a4b2..47892a94d5 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java @@ -11,17 +11,20 @@ public class ConceptSetPermissionSchema extends EntityPermissionSchema { private static Map writePermissions = new HashMap() {{ put("conceptset:%s:put", "Update Concept Set with ID = %s"); put("conceptset:%s:items:put", "Update Items of Concept Set with ID = %s"); + put("conceptset:%s:annotation:*:delete", "Delete Annotations of Concept Set with ID = %s"); + put("conceptset:*:annotation:*:delete", "Delete Annotations of any Concept Set"); put("conceptset:%s:delete", "Delete Concept Set with ID = %s"); }}; private static Map readPermissions = new HashMap() {{ put("conceptset:%s:get", "view conceptset definition with id %s"); put("conceptset:%s:expression:get", "Resolve concept set %s expression"); + put("conceptset:%s:annotation:get", "Resolve concept set annotations"); + put("conceptset:*:annotation:get", "Resolve concept set annotations"); put("conceptset:%s:version:*:expression:get", "Get expression for concept set %s items for default source"); }}; public ConceptSetPermissionSchema() { - super(EntityType.CONCEPT_SET, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java index b80e9e7e44..c294cdb17a 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EntityType.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EntityType.java @@ -4,7 +4,6 @@ import org.ohdsi.webapi.cohortdefinition.CohortDefinition; import org.ohdsi.webapi.cohortsample.CohortSample; import org.ohdsi.webapi.conceptset.ConceptSet; -import org.ohdsi.webapi.conceptset.annotation.ConceptSetAnnotation; import org.ohdsi.webapi.estimation.Estimation; import org.ohdsi.webapi.feanalysis.domain.FeAnalysisEntity; import org.ohdsi.webapi.ircalc.IncidenceRateAnalysis; @@ -27,9 +26,7 @@ public enum EntityType { PREDICTION(PredictionAnalysis.class), COHORT_SAMPLE(CohortSample.class), TAG(Tag.class), - REUSABLE(Reusable.class), - CONCEPT_SET_ANNOTATION(ConceptSetAnnotation.class); - + REUSABLE(Reusable.class); private final Class entityClass; EntityType(Class entityClass) { diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index a606928f6d..6f2911e451 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -913,8 +913,6 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, conceptSetAnnotation.setVocabularyVersion(newAnnotationData.getVocabularyVersion()); conceptSetAnnotation.setConceptSetVersion(newAnnotationData.getConceptSetVersion()); conceptSetAnnotation.setConceptId(newAnnotationData.getConceptId()); - conceptSetAnnotation.setCreatedBy(getCurrentUser()); - conceptSetAnnotation.setCreatedDate(new Date()); return conceptSetAnnotation; }).collect(Collectors.toList()); @@ -937,10 +935,6 @@ private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSe targetConceptSetAnnotation.setAnnotationDetails(sourceConceptSetAnnotation.getAnnotationDetails()); targetConceptSetAnnotation.setConceptId(sourceConceptSetAnnotation.getConceptId()); targetConceptSetAnnotation.setVocabularyVersion(sourceConceptSetAnnotation.getVocabularyVersion()); - targetConceptSetAnnotation.setCreatedBy(sourceConceptSetAnnotation.getCreatedBy()); - targetConceptSetAnnotation.setCreatedDate(sourceConceptSetAnnotation.getCreatedDate()); - targetConceptSetAnnotation.setModifiedBy(sourceConceptSetAnnotation.getModifiedBy()); - targetConceptSetAnnotation.setModifiedDate(sourceConceptSetAnnotation.getModifiedDate()); targetConceptSetAnnotation.setCopiedFromConceptSetIds(appendCopiedFromConceptSetId(sourceConceptSetAnnotation.getCopiedFromConceptSetIds(), sourceConceptSetId)); return targetConceptSetAnnotation; } @@ -995,19 +989,16 @@ private AnnotationDTO convertAnnotationEntityToDTO(ConceptSetAnnotation conceptS annotationDTO.setVocabularyVersion(conceptSetAnnotation.getVocabularyVersion()); annotationDTO.setConceptSetVersion(conceptSetAnnotation.getConceptSetVersion()); annotationDTO.setCopiedFromConceptSetIds(conceptSetAnnotation.getCopiedFromConceptSetIds()); - annotationDTO.setCreatedBy(conceptSetAnnotation.getCreatedBy() != null ? conceptSetAnnotation.getCreatedBy().getName() : null); - annotationDTO.setCreatedDate(conceptSetAnnotation.getCreatedDate() != null ? conceptSetAnnotation.getCreatedDate().toString() : null); - return annotationDTO; } @DELETE - @Path("/annotation/{id}") + @Path("/{conceptSetId}/annotation/{annotationId}") @Produces(MediaType.APPLICATION_JSON) - public Response deleteConceptSetAnnotation(@PathParam("id") final int id) { - ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository().findById(id); + public Response deleteConceptSetAnnotation(@PathParam("conceptSetId") final int conceptSetId, @PathParam("annotationId") final int annotationId) { + ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository().findById(annotationId); if (conceptSetAnnotation != null) { - getConceptSetAnnotationRepository().deleteById(id); + getConceptSetAnnotationRepository().deleteById(annotationId); return Response.ok().build(); } else throw new NotFoundException("Concept set annotation not found"); } @@ -1020,8 +1011,6 @@ public AnnotationDTO updateConceptSetAnnotation(@PathParam("id") final int id, A .findConceptSetAnnotationByConceptIdAndConceptId(id, annotationDTO.getConceptId()) .orElseThrow(() -> new RuntimeException("Concept set annotation not found")); conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDTO)); - conceptSetAnnotation.setModifiedBy(getCurrentUser()); - conceptSetAnnotation.setModifiedDate(new Date()); getConceptSetAnnotationRepository().save(conceptSetAnnotation); return annotationDTO; } From 4ee6b48bc665449ca147d771d6de5840acd22e27 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Sat, 9 Nov 2024 12:05:00 +0100 Subject: [PATCH 17/19] Added migration script to re-create annotation-related permissions. Adressed comments from the feature review on the community github # Conflicts: # src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java --- .../ohdsi/webapi/conceptset/ConceptSet.java | 2 +- .../model/ConceptSetPermissionSchema.java | 1 + .../webapi/service/ConceptSetService.java | 47 +++++++------------ src/main/resources/i18n/messages_en.json | 3 +- src/main/resources/i18n/messages_ko.json | 3 +- src/main/resources/i18n/messages_ru.json | 3 +- src/main/resources/i18n/messages_zh.json | 3 +- 7 files changed, 22 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java index fb360e0d85..e19f97ebca 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/ConceptSet.java @@ -37,7 +37,7 @@ * * @author fdefalco */ -@Entity(name="ConceptSet") +@Entity(name = "ConceptSet") @Table(name="concept_set") public class ConceptSet extends CommonEntityExt implements Serializable { diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java index 47892a94d5..e5d9c72cdc 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java @@ -11,6 +11,7 @@ public class ConceptSetPermissionSchema extends EntityPermissionSchema { private static Map writePermissions = new HashMap() {{ put("conceptset:%s:put", "Update Concept Set with ID = %s"); put("conceptset:%s:items:put", "Update Items of Concept Set with ID = %s"); + put("conceptset:*:annotation:put", "Create Concept Set Annotation"); put("conceptset:%s:annotation:*:delete", "Delete Annotations of Concept Set with ID = %s"); put("conceptset:*:annotation:*:delete", "Delete Annotations of any Concept Set"); put("conceptset:%s:delete", "Delete Concept Set with ID = %s"); diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 6f2911e451..b5f4e90666 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -16,7 +16,6 @@ package org.ohdsi.webapi.service; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -29,8 +28,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.UnauthorizedException; import org.ohdsi.circe.vocabulary.ConceptSetExpression; import org.ohdsi.vocabulary.Concept; @@ -895,7 +892,7 @@ private ConceptSetVersion saveVersion(int id) { @Transactional public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, SaveConceptSetAnnotationsRequest request) { removeAnnotations(conceptSetId, request); - if (CollectionUtils.isNotEmpty(request.getNewAnnotation())) { + if (request.getNewAnnotation() == null || request.getNewAnnotation().isEmpty()) { List annotationList = request.getNewAnnotation() .stream() .map(newAnnotationData -> { @@ -908,6 +905,7 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, annotationDetailsDTO.setSearchData(newAnnotationData.getSearchData()); conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDetailsDTO)); } catch (JsonProcessingException e) { + log.error("Could not serialize Concept Set AnnotationDetailsDTO", e); throw new RuntimeException(e); } conceptSetAnnotation.setVocabularyVersion(newAnnotationData.getVocabularyVersion()); @@ -922,12 +920,23 @@ public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, return true; } private void removeAnnotations(int id, SaveConceptSetAnnotationsRequest request){ - if (CollectionUtils.isNotEmpty(request.getRemoveAnnotation())) { + if (request.getRemoveAnnotation() != null && !request.getRemoveAnnotation().isEmpty()) { for (AnnotationDTO annotationDTO : request.getRemoveAnnotation()) { this.getConceptSetAnnotationRepository().deleteAnnotationByConceptSetIdAndConceptId(id, annotationDTO.getConceptId()); } } } + @POST + @Path("/copy-annotations") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { + List sourceAnnotations = getConceptSetAnnotationRepository().findByConceptSetId(copyAnnotationsRequest.getSourceConceptSetId()); + List copiedAnnotations= sourceAnnotations.stream() + .map(sourceAnnotation -> copyAnnotation(sourceAnnotation, copyAnnotationsRequest.getSourceConceptSetId(), copyAnnotationsRequest.getTargetConceptSetId())) + .collect(Collectors.toList()); + getConceptSetAnnotationRepository().save(copiedAnnotations); + } private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSetAnnotation, int sourceConceptSetId, int targetConceptSetId){ ConceptSetAnnotation targetConceptSetAnnotation = new ConceptSetAnnotation(); targetConceptSetAnnotation.setConceptSetId(targetConceptSetId); @@ -938,26 +947,13 @@ private ConceptSetAnnotation copyAnnotation(ConceptSetAnnotation sourceConceptSe targetConceptSetAnnotation.setCopiedFromConceptSetIds(appendCopiedFromConceptSetId(sourceConceptSetAnnotation.getCopiedFromConceptSetIds(), sourceConceptSetId)); return targetConceptSetAnnotation; } - private String appendCopiedFromConceptSetId(String copiedFromConceptSetIds, int sourceConceptSetId) { - if(StringUtils.isEmpty(copiedFromConceptSetIds)){ + if(copiedFromConceptSetIds == null || copiedFromConceptSetIds.isEmpty()){ return Integer.toString(sourceConceptSetId); } return copiedFromConceptSetIds.concat(",").concat(Integer.toString(sourceConceptSetId)); } - @POST - @Path("/copy-annotations") - @Produces(MediaType.APPLICATION_JSON) - @Transactional - public void copyAnnotations(CopyAnnotationsRequest copyAnnotationsRequest ) { - List sourceAnnotations = getConceptSetAnnotationRepository().findByConceptSetId(copyAnnotationsRequest.getSourceConceptSetId()); - List copiedAnnotations= sourceAnnotations.stream() - .map(sourceAnnotation -> copyAnnotation(sourceAnnotation, copyAnnotationsRequest.getSourceConceptSetId(), copyAnnotationsRequest.getTargetConceptSetId())) - .collect(Collectors.toList()); - getConceptSetAnnotationRepository().save(copiedAnnotations); - } - @GET @Path("/{id}/annotation") @Produces(MediaType.APPLICATION_JSON) @@ -974,6 +970,7 @@ private AnnotationDTO convertAnnotationEntityToDTO(ConceptSetAnnotation conceptS try { annotationDetails = mapper.readValue(conceptSetAnnotation.getAnnotationDetails(), AnnotationDetailsDTO.class); } catch (JsonProcessingException e) { + log.error("Could not deserialize Concept Set AnnotationDetailsDTO", e); throw new RuntimeException(e); } @@ -1002,16 +999,4 @@ public Response deleteConceptSetAnnotation(@PathParam("conceptSetId") final int return Response.ok().build(); } else throw new NotFoundException("Concept set annotation not found"); } - - @PUT - @Path("/update/{id}/annotation") - @Produces(MediaType.APPLICATION_JSON) - public AnnotationDTO updateConceptSetAnnotation(@PathParam("id") final int id, AnnotationDTO annotationDTO) throws IOException { - ConceptSetAnnotation conceptSetAnnotation = getConceptSetAnnotationRepository() - .findConceptSetAnnotationByConceptIdAndConceptId(id, annotationDTO.getConceptId()) - .orElseThrow(() -> new RuntimeException("Concept set annotation not found")); - conceptSetAnnotation.setAnnotationDetails(mapper.writeValueAsString(annotationDTO)); - getConceptSetAnnotationRepository().save(conceptSetAnnotation); - return annotationDTO; - } } \ No newline at end of file diff --git a/src/main/resources/i18n/messages_en.json b/src/main/resources/i18n/messages_en.json index 19fba9756e..e4ea6edf60 100644 --- a/src/main/resources/i18n/messages_en.json +++ b/src/main/resources/i18n/messages_en.json @@ -1791,8 +1791,7 @@ "includedConcepts": "Included Concepts", "includedSourceCodes": "Included Source Codes", "versions": "Versions", - "messages": "Messages", - "metadata": "Metadata" + "messages": "Messages" }, "concept": { "title": "Vocabulary > Concept", diff --git a/src/main/resources/i18n/messages_ko.json b/src/main/resources/i18n/messages_ko.json index c97f79854f..9ec9712441 100644 --- a/src/main/resources/i18n/messages_ko.json +++ b/src/main/resources/i18n/messages_ko.json @@ -1791,8 +1791,7 @@ "includedConcepts": "포함된 컨셉", "includedSourceCodes": "포함된 소스 코드", "versions": "Versions", - "messages": "메시지", - "metadata": "Metadata" + "messages": "메시지" }, "concept": { "title": "어휘(Vocabulary) > 컨셉", diff --git a/src/main/resources/i18n/messages_ru.json b/src/main/resources/i18n/messages_ru.json index 1cd36105ca..aae125875e 100644 --- a/src/main/resources/i18n/messages_ru.json +++ b/src/main/resources/i18n/messages_ru.json @@ -1701,8 +1701,7 @@ "includedConcepts": "Включенные концепции", "exploreEvidence": "Исследовать Свидетельство", "versions": "История версий", - "messages": "Сообщения", - "metadata": "Metadata" + "messages": "Сообщения" }, "exploreEvidence": { "title": "Исследование Свидетельства:", diff --git a/src/main/resources/i18n/messages_zh.json b/src/main/resources/i18n/messages_zh.json index 87a5183ae0..207cfcd706 100644 --- a/src/main/resources/i18n/messages_zh.json +++ b/src/main/resources/i18n/messages_zh.json @@ -1791,8 +1791,7 @@ "includedConcepts": "包含的概念", "includedSourceCodes": "包含的源代码", "versions": "Versions", - "messages": "提示信息", - "metadata": "Metadata" + "messages": "提示信息" }, "concept": { "title": "术语集 > 概念", From 57491c3d11ecf7f29cac3af23a57c6004542f3b4 Mon Sep 17 00:00:00 2001 From: oleg-odysseus Date: Sat, 9 Nov 2024 14:04:16 +0100 Subject: [PATCH 18/19] Changed SearchDataTransformer in accordance with the community discussion comments --- .../annotation/ConceptSetAnnotation.java | 6 +- .../webapi/service/ConceptSetService.java | 2 +- .../annotations/SearchDataTransformer.java | 117 +++++++++--------- .../webapi/service/dto/AnnotationDTO.java | 6 +- .../SearchDataTransformerTest.java | 29 ++--- 5 files changed, 73 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java index df70466c50..f13fd19db7 100644 --- a/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java +++ b/src/main/java/org/ohdsi/webapi/conceptset/annotation/ConceptSetAnnotation.java @@ -45,7 +45,7 @@ public class ConceptSetAnnotation implements Serializable { private String vocabularyVersion; @Column(name = "concept_set_version") - private String conceptSetVersion; + private Integer conceptSetVersion; @Column(name = "copied_from_concept_set_ids") private String copiedFromConceptSetIds; @@ -90,11 +90,11 @@ public void setVocabularyVersion(String vocabularyVersion) { this.vocabularyVersion = vocabularyVersion; } - public String getConceptSetVersion() { + public Integer getConceptSetVersion() { return conceptSetVersion; } - public void setConceptSetVersion(String conceptSetVersion) { + public void setConceptSetVersion(Integer conceptSetVersion) { this.conceptSetVersion = conceptSetVersion; } diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index b5f4e90666..84bc8e7787 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -892,7 +892,7 @@ private ConceptSetVersion saveVersion(int id) { @Transactional public boolean saveConceptSetAnnotation(@PathParam("id") final int conceptSetId, SaveConceptSetAnnotationsRequest request) { removeAnnotations(conceptSetId, request); - if (request.getNewAnnotation() == null || request.getNewAnnotation().isEmpty()) { + if (request.getNewAnnotation() != null && !request.getNewAnnotation().isEmpty()) { List annotationList = request.getNewAnnotation() .stream() .map(newAnnotationData -> { diff --git a/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java b/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java index 309b3253c3..e013adfb0f 100644 --- a/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java +++ b/src/main/java/org/ohdsi/webapi/service/annotations/SearchDataTransformer.java @@ -5,9 +5,9 @@ import org.json.JSONObject; import org.springframework.stereotype.Service; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; @Service public class SearchDataTransformer { @@ -15,91 +15,86 @@ public class SearchDataTransformer { private static final String FILTER_DATA = "filterData"; private static final String TITLE = "title"; private static final String VALUE = "value"; + private static final String KEY = "key"; private static final String FILTER_SOURCE = "filterSource"; - private static final String FILTER_SOURCE_LABEL = "Filter Source"; + private static final String FILTER_SOURCE_LABEL = "Filtered By"; private static final String SEARCH_TEXT = "searchText"; - private static final String SEARCH_TEXT_LABEL = "Search Text"; - private static final String FILTER_COLUMNS = "filterColumns"; - private static final String QUOTE = "\""; - + private static final String DEFAULT_FILTER_SOURCE = "Search"; private static final String DELIMITER = ", "; - private static final String ENTRY_FORMAT = "%s: \"%s\""; public String convertJsonToReadableFormat(String jsonInput) { - JSONObject searchObject = new JSONObject(jsonInput); - StringBuilder result = new StringBuilder(); + JSONObject searchObject = new JSONObject(Optional.ofNullable(jsonInput).orElse("{}")); - String filterDataResult = extractFilterData(searchObject); - appendCommaSeparated(result, filterDataResult); - - String filterSourceResult = processFilterSource(searchObject); - appendCommaSeparated(result, filterSourceResult); + if (searchObject.isEmpty()) { + return ""; + } - return result.toString(); - } + StringBuilder result = new StringBuilder(); - private String extractFilterData(JSONObject jsonObject) { - JSONObject filterData = jsonObject.optJSONObject(FILTER_DATA); - if (filterData != null) { - String searchText = extractSearchText(filterData); - String filterColumns = extractFilterColumns(filterData); - return Stream.of(searchText, filterColumns) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.joining(DELIMITER)); - } - JSONArray filterDataArray = jsonObject.optJSONArray(FILTER_DATA); - if (filterDataArray != null) { - return formatKeyValuePairs(filterDataArray); + String filterSource = processFilterSource(searchObject); + append(result, getDefaultOrActual(filterSource, DEFAULT_FILTER_SOURCE)); + + JSONObject filterDataObject = searchObject.optJSONObject(FILTER_DATA); + JSONArray filterDataArray = searchObject.optJSONArray(FILTER_DATA); + + if (filterDataObject != null) { + Optional.ofNullable(filterDataObject).map(this::processSearchText).ifPresent(searchText -> appendCommaSeparated(result, formatQuoted(searchText))); + Optional.ofNullable(filterDataObject.optJSONArray("filterColumns")).map(this::formatKeyValuePairs).ifPresent( + fdResult -> appendCommaSeparated(result, FILTER_SOURCE_LABEL + ": \"" + fdResult + "\"") + ); + } else if (filterDataArray != null) { + String extractedData = formatKeyValuePairs(filterDataArray); + if (!extractedData.isEmpty()) { + appendCommaSeparated(result, FILTER_SOURCE_LABEL + ": \"" + extractedData + "\""); + } } - return ""; - } - private String extractFilterColumns(JSONObject filterData) { - JSONArray filterColumns = filterData.optJSONArray(FILTER_COLUMNS); - if (filterColumns != null) { - return formatKeyValuePairs(filterColumns); - } - return ""; + return result.toString().trim(); } private String processFilterSource(JSONObject jsonObject) { - String filterSource = jsonObject.optString(FILTER_SOURCE, ""); - if (!filterSource.isEmpty()) { - return String.format(ENTRY_FORMAT, FILTER_SOURCE_LABEL, filterSource); - } - return ""; + return jsonObject.optString(FILTER_SOURCE, ""); } - private String extractSearchText(JSONObject jsonObject) { - String searchText = jsonObject.optString(SEARCH_TEXT, ""); - if (!searchText.isEmpty()) { - return String.format(ENTRY_FORMAT, SEARCH_TEXT_LABEL, searchText); - } - return ""; + private String processSearchText(JSONObject filterData) { + return filterData.optString(SEARCH_TEXT, ""); } - private String formatKeyValuePairs(JSONArray filterColumns) { - return IntStream.range(0, filterColumns.length()) - .mapToObj(index -> { - JSONObject item = filterColumns.getJSONObject(index); - String title = optString(item, TITLE); - String value = StringUtils.unwrap(optString(item, VALUE), QUOTE); - return String.format(ENTRY_FORMAT, title, value); - }) + private String formatKeyValuePairs(JSONArray filterDataArray) { + return IntStream.range(0, filterDataArray.length()) + .mapToObj(index -> formatEntry(filterDataArray.getJSONObject(index))) .collect(Collectors.joining(DELIMITER)); } - private void appendCommaSeparated(StringBuilder resultBuilder, String part) { + private String formatEntry(JSONObject item) { + String title = optString(item, TITLE); + String key = StringUtils.unwrap(optString(item, KEY), '"'); + return String.format(ENTRY_FORMAT, title, key); + } + + private void appendCommaSeparated(StringBuilder builder, String part) { if (!part.isEmpty()) { - if (resultBuilder.length() > 0) { - resultBuilder.append(DELIMITER); - } - resultBuilder.append(part); + append(builder, part); } } + private void append(StringBuilder builder, String part) { + if (builder.length() > 0) { + builder.append(DELIMITER); + } + builder.append(part); + } + private String optString(JSONObject item, String key) { return item.optString(key, ""); } -} + + private String getDefaultOrActual(String actual, String defaultVal) { + return actual.isEmpty() ? defaultVal : actual; + } + + private String formatQuoted(String text) { + return String.format("\"%s\"", text); + } +} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java index bf1e2ac700..0ed0808034 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/AnnotationDTO.java @@ -9,7 +9,7 @@ public class AnnotationDTO { private String createdBy; private String createdDate; private String vocabularyVersion; - private String conceptSetVersion; + private Integer conceptSetVersion; private String searchData; private String copiedFromConceptSetIds; private Integer conceptId; @@ -38,11 +38,11 @@ public void setVocabularyVersion(String vocabularyVersion) { this.vocabularyVersion = vocabularyVersion; } - public String getConceptSetVersion() { + public Integer getConceptSetVersion() { return conceptSetVersion; } - public void setConceptSetVersion(String conceptSetVersion) { + public void setConceptSetVersion(Integer conceptSetVersion) { this.conceptSetVersion = conceptSetVersion; } diff --git a/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java b/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java index 104450f3c8..ea24b5ac76 100644 --- a/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java +++ b/src/test/java/org/ohdsi/webapi/service/annotations/SearchDataTransformerTest.java @@ -5,10 +5,8 @@ import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyString; -import static org.hamcrest.Matchers.not; public class SearchDataTransformerTest { @@ -30,50 +28,43 @@ public void shouldReturnEmptyStringWhenInputIsEmpty() { public void shouldHandleSearchText() { String input = "{\"filterData\":{\"searchText\":\"testSearch\"}}"; String result = sut.convertJsonToReadableFormat(input); - assertThat(result, is("Search Text: \"testSearch\"")); + assertThat(result, is("Search, \"testSearch\"")); } @Test public void shouldHandleFilterSource() { String input = "{\"filterSource\":\"Search\"}"; String result = sut.convertJsonToReadableFormat(input); - assertThat(result, is("Filter Source: \"Search\"")); + assertThat(result, is("Search")); } @Test public void shouldHandleFilterColumns() { - String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"}]} }"; + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"key\":\"Drug\"}]} }"; String result = sut.convertJsonToReadableFormat(input); - assertThat(result, is("Domain: \"Drug\"")); + assertThat(result, is("Search, \"\", Filtered By: \"Domain: \"Drug\"\"")); } @Test public void shouldCombineFilterDataAndFilterSource() { - String input = "{\"filterData\":{\"searchText\":\"testSearch\",\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"}]},\"filterSource\":\"Search\"}"; + String input = "{\"filterData\":{\"searchText\":\"testSearch\",\"filterColumns\":[{\"title\":\"Domain\",\"key\":\"Drug\"}]},\"filterSource\":\"Search\"}"; String result = sut.convertJsonToReadableFormat(input); - String expected = "Search Text: \"testSearch\", Domain: \"Drug\", Filter Source: \"Search\""; + String expected = "Search, \"testSearch\", Filtered By: \"Domain: \"Drug\"\""; assertThat(result, is(expected)); } @Test public void shouldHandleMultipleFilterColumns() { - String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"value\":\"Drug\"},{\"title\":\"Class\",\"value\":\"Medication\"}]}}"; + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":\"Domain\",\"key\":\"Drug\"},{\"title\":\"Class\",\"key\":\"Medication\"}]}}"; String result = sut.convertJsonToReadableFormat(input); - String expected = "Domain: \"Drug\", Class: \"Medication\""; + String expected = "Search, \"\", Filtered By: \"Domain: \"Drug\"" + ", Class: \"Medication\"\""; assertThat(result, is(expected)); } - @Test - public void shouldIgnoreEmptyFilterColumnsAndSearchText() { - String input = "{\"filterData\":{\"searchText\":\"\",\"filterColumns\":[]}, \"filterSource\":\"\"}"; - String result = sut.convertJsonToReadableFormat(input); - assertThat(result, isEmptyString()); - } - @Test public void shouldHandleNullValuesGracefully() { - String input = "{\"filterData\":{\"filterColumns\":[{\"title\":null,\"value\":null}], \"searchText\":null}, \"filterSource\":null}"; + String input = "{\"filterData\":{\"filterColumns\":[{\"title\":null,\"key\":null}], \"searchText\":null}, \"filterSource\":null}"; String result = sut.convertJsonToReadableFormat(input); - assertThat(result, not(containsString("null"))); + assertThat(result, is("Search, \"\", Filtered By: \": \"\"\"")); } } \ No newline at end of file From 4c3a304d3a61ebd4ff92c0f251ab0a3a0b9bafb8 Mon Sep 17 00:00:00 2001 From: alex-odysseus Date: Tue, 3 Dec 2024 11:56:41 +0100 Subject: [PATCH 19/19] Permissions update, concept_set_version column type change to integer --- ...20240716100000__conceptset_annotations.sql | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql b/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql index fd39cece3e..04da4e9153 100644 --- a/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql +++ b/src/main/resources/db/migration/postgresql/V2.15.0.20240716100000__conceptset_annotations.sql @@ -19,27 +19,39 @@ CREATE TABLE ${ohdsiSchema}.concept_set_annotation ON DELETE CASCADE ); +DELETE FROM ${ohdsiSchema}.sec_role_permission +WHERE permission_id IN ( + SELECT id FROM ${ohdsiSchema}.sec_permission + WHERE value like '%:annotation:%' +); + +DELETE FROM ${ohdsiSchema}.sec_permission +WHERE value like '%:annotation:%'; + INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:put', 'Create Concept Set Annotation'); + INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:annotation:*:delete', 'Delete Concept Set Annotation'); -INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotation'); + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:get', 'List Concept Set Annotations'); + INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:get', 'View Concept Set Annotation'); + INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES - (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:copy-annotations', 'Copy Concept Set Annotations'); + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:%s:annotation:*:delete', 'Delete Owner`s Concept Set Annotations'); +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'conceptset:*:annotation:*:delete', 'Delete Any Concept Set Annotation'); INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', - 'conceptset:annotation:*:delete', + 'conceptset:*:annotation:*:delete', + 'conceptset:%s:annotation:*:delete', 'conceptset:%s:annotation:get', - 'conceptset:*:annotation:get', - 'conceptset:copy-annotations' + 'conceptset:*:annotation:get' ) AND sr.name IN ('admin'); INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) @@ -47,7 +59,9 @@ SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr WHERE sp.value IN ( 'conceptset:*:annotation:put', + 'conceptset:%s:annotation:*:delete', 'conceptset:%s:annotation:get', - 'conceptset:*:annotation:get', - 'conceptset:copy-annotations' - ) AND sr.name IN ('Atlas users'); \ No newline at end of file + 'conceptset:*:annotation:get' + ) AND sr.name IN ('Atlas users'); + +ALTER TABLE ${ohdsiSchema}.concept_set_annotation ALTER COLUMN concept_set_version TYPE INTEGER USING (concept_set_version::integer); \ No newline at end of file