diff --git a/docs/processes/project-lifecycles.md b/docs/processes/project-lifecycles.md new file mode 100644 index 000000000..6520b3b68 --- /dev/null +++ b/docs/processes/project-lifecycles.md @@ -0,0 +1,17 @@ +#bla + +blubb + +```mermaid +flowchart TB + start((Start)) + cproject["Service call for create project"] + cprojectDB["Create the project in the data base"] + cprojectOpBis["Create the project in OpenBIS"] + start --> cproject + cproject --> cprojectDB + cprojectDB --> cprojectOpBis + cprojectOpBis -- rollback -->cprojectDB + + cprojectDB -- rollback --> start +``` diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/batch/BatchJpaRepository.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/batch/BatchJpaRepository.java index a6a8f546d..fdc2d2dac 100644 --- a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/batch/BatchJpaRepository.java +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/batch/BatchJpaRepository.java @@ -14,7 +14,7 @@ import life.qbic.projectmanagement.domain.model.experiment.ExperimentId; import life.qbic.projectmanagement.domain.model.sample.Sample; import life.qbic.projectmanagement.domain.repository.BatchRepository; -import life.qbic.projectmanagement.infrastructure.sample.QbicSampleRepository; +import life.qbic.projectmanagement.infrastructure.sample.SampleJpaRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -31,11 +31,11 @@ public class BatchJpaRepository implements BatchRepository { private static final Logger log = logger(BatchJpaRepository.class); private final QbicBatchRepo qbicBatchRepo; - private final QbicSampleRepository qbicSampleRepository; + private final SampleJpaRepository qbicSampleRepository; @Autowired public BatchJpaRepository(QbicBatchRepo qbicBatchRepo, - QbicSampleRepository qbicSampleRepository) { + SampleJpaRepository qbicSampleRepository) { this.qbicBatchRepo = qbicBatchRepo; this.qbicSampleRepository = qbicSampleRepository; } diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableJpaRepository.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableJpaRepository.java new file mode 100644 index 000000000..d2124d746 --- /dev/null +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableJpaRepository.java @@ -0,0 +1,16 @@ +package life.qbic.projectmanagement.infrastructure.confounding; + +import java.util.Collection; +import java.util.List; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableData; +import org.springframework.data.repository.ListCrudRepository; + +public interface ConfoundingVariableJpaRepository extends + ListCrudRepository { + + long countByExperimentIdEquals(String experimentId); + + List findAllByExperimentIdEquals(String experimentId); + + boolean existsDistinctByIdIsIn(Collection ids); +} diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelJpaRepository.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelJpaRepository.java new file mode 100644 index 000000000..616898076 --- /dev/null +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelJpaRepository.java @@ -0,0 +1,24 @@ +package life.qbic.projectmanagement.infrastructure.confounding; + +import java.util.List; +import java.util.Optional; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableLevelData; +import org.springframework.data.repository.ListCrudRepository; + +public interface ConfoundingVariableLevelJpaRepository extends + ListCrudRepository { + + List findAllBySampleIdEquals(String sampleId); + + List findAllByVariableIdEquals(long variableId); + + List findAllByVariableIdIn(List variableIds); + + Optional findBySampleIdEqualsAndVariableIdEquals(String sampleId, + long variableId); + + long countByVariableIdEquals(long variableId); + + void deleteAllByVariableIdEquals(long variableId); + +} diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelRepositoryImpl.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelRepositoryImpl.java new file mode 100644 index 000000000..8c83e72fc --- /dev/null +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableLevelRepositoryImpl.java @@ -0,0 +1,84 @@ +package life.qbic.projectmanagement.infrastructure.confounding; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableLevelData; +import life.qbic.projectmanagement.domain.repository.ConfoundingVariableLevelRepository; +import org.springframework.stereotype.Repository; + +/** + * The implementation of the {@link ConfoundingVariableLevelRepository} interface. + * Delegates method calls to the corresponding JPA repositories. + * @since 1.6.0 + */ +@Repository +public class ConfoundingVariableLevelRepositoryImpl implements ConfoundingVariableLevelRepository { + + private final ConfoundingVariableLevelJpaRepository jpaRepository; + + public ConfoundingVariableLevelRepositoryImpl( + ConfoundingVariableLevelJpaRepository jpaRepository) { + this.jpaRepository = jpaRepository; + } + + @Override + public List findAllForSample(String projectId, String sampleId) { + return jpaRepository.findAllBySampleIdEquals(sampleId); + } + + @Override + public List findAllForVariable(String projectId, + long variableId) { + return jpaRepository.findAllByVariableIdEquals(variableId); + } + + + @Override + public List findAllForVariables(String projectId, + List variableIds) { + return jpaRepository.findAllByVariableIdIn(variableIds); + } + + @Override + public Optional findVariableLevelOfSample(String projectId, + String sampleId, long variableId) { + return jpaRepository.findBySampleIdEqualsAndVariableIdEquals(sampleId, variableId); + } + + + @Override + public S save(String projectId, S entity) { + return jpaRepository.save(entity); + } + + @Override + public Optional findById(String projectId, Long aLong) { + return jpaRepository.findById(aLong); + } + + @Override + public long countLevelsOfVariable(String projectId, long variableId) { + return jpaRepository.countByVariableIdEquals(variableId); + } + + @Override + public void deleteById(String projectId, Long aLong) { + jpaRepository.deleteById(aLong); + } + + @Override + public void deleteAllForSample(String projectId, String sampleId) { + List allBySampleIdEquals = jpaRepository.findAllBySampleIdEquals( + sampleId); + Set levelIds = allBySampleIdEquals.stream().map(ConfoundingVariableLevelData::getId) + .collect(Collectors.toSet()); + jpaRepository.deleteAllById(levelIds); + } + + @Override + public void deleteAllForVariable(String projectId, long variableId) { + jpaRepository.deleteAllByVariableIdEquals(variableId); + } +} diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableRepositoryImpl.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableRepositoryImpl.java new file mode 100644 index 000000000..1bb2131e9 --- /dev/null +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/confounding/ConfoundingVariableRepositoryImpl.java @@ -0,0 +1,69 @@ +package life.qbic.projectmanagement.infrastructure.confounding; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableData; +import life.qbic.projectmanagement.domain.repository.ConfoundingVariableRepository; +import org.springframework.stereotype.Repository; + +/** + * The implementation of the {@link ConfoundingVariableRepository} interface. + * Delegates method calls to the corresponding JPA repositories. + * @since 1.6.0 + */ +@Repository +public class ConfoundingVariableRepositoryImpl implements ConfoundingVariableRepository { + + private final ConfoundingVariableJpaRepository jpaRepository; + + public ConfoundingVariableRepositoryImpl(ConfoundingVariableJpaRepository jpaRepository) { + this.jpaRepository = jpaRepository; + } + + @Override + public List saveAll(String projectId, + Iterable entities) { + return jpaRepository.saveAll(entities); + } + + @Override + public List findAll(String projectId, String experimentId) { + return jpaRepository.findAllByExperimentIdEquals(experimentId); + } + + @Override + public List findAllById(String projectId, Iterable longs) { + return jpaRepository.findAllById(longs); + } + + @Override + public boolean existsAllById(String projectId, Collection longs) { + return jpaRepository.existsDistinctByIdIsIn(longs); + } + + @Override + public S save(String projectId, S entity) { + return jpaRepository.save(entity); + } + + @Override + public Optional findById(String projectId, Long aLong) { + return jpaRepository.findById(aLong); + } + + @Override + public long countVariablesOfExperiment(String projectId, String experimentId) { + return jpaRepository.countByExperimentIdEquals(experimentId); + } + + @Override + public void deleteById(String projectId, Long aLong) { + jpaRepository.deleteById(aLong); + } + + @Override + public void deleteAllById(String projectId, Iterable longs) { + jpaRepository.deleteAllById(longs); + } +} diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleDataRepo.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleDataRepository.java similarity index 97% rename from project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleDataRepo.java rename to project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleDataRepository.java index 9719a97e5..57350f665 100644 --- a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleDataRepo.java +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleDataRepository.java @@ -15,7 +15,7 @@ * * @since 1.0.0 */ -public interface QbicSampleDataRepo { +public interface SampleDataRepository { /** * Creates a reference to one or more {@link Sample}s in the data repository to connect project data. diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleRepository.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleJpaRepository.java similarity index 90% rename from project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleRepository.java rename to project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleJpaRepository.java index 42f3019e8..4970a450d 100644 --- a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/QbicSampleRepository.java +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleJpaRepository.java @@ -9,7 +9,7 @@ import life.qbic.projectmanagement.domain.model.sample.SampleId; import org.springframework.data.jpa.repository.JpaRepository; -public interface QbicSampleRepository extends JpaRepository { +public interface SampleJpaRepository extends JpaRepository { Collection findAllByExperimentId(ExperimentId experimentId); diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleRepositoryImpl.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleRepositoryImpl.java index 84dadea8d..1f9ed38f5 100644 --- a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleRepositoryImpl.java +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/SampleRepositoryImpl.java @@ -10,9 +10,7 @@ import life.qbic.application.commons.ApplicationException; import life.qbic.application.commons.ApplicationException.ErrorCode; import life.qbic.application.commons.ApplicationException.ErrorParameters; -import life.qbic.application.commons.Result; import life.qbic.logging.api.Logger; -import life.qbic.projectmanagement.application.sample.SampleInformationService; import life.qbic.projectmanagement.domain.model.batch.BatchId; import life.qbic.projectmanagement.domain.model.experiment.ExperimentId; import life.qbic.projectmanagement.domain.model.project.Project; @@ -22,10 +20,11 @@ import life.qbic.projectmanagement.domain.model.sample.SampleId; import life.qbic.projectmanagement.domain.repository.ProjectRepository; import life.qbic.projectmanagement.domain.repository.SampleRepository; -import life.qbic.projectmanagement.domain.service.SampleDomainService.ResponseCode; import life.qbic.projectmanagement.infrastructure.sample.openbis.OpenbisConnector.SampleNotDeletedException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -37,10 +36,10 @@ *

This class serves as an adapter and proxies requests to an JPA implementation to interact * with persistent {@link Sample} data in the storage layer. * - *

The actual JPA implementation is done by {@link QbicSampleRepository}, which is injected as + *

The actual JPA implementation is done by {@link SampleJpaRepository}, which is injected as * dependency upon creation. *

- * Also handles project storage in openBIS through {@link QbicSampleDataRepo} + * Also handles project storage in openBIS through {@link SampleDataRepository} * * @since 1.0.0 */ @@ -48,48 +47,46 @@ public class SampleRepositoryImpl implements SampleRepository { private static final Logger log = logger(SampleRepositoryImpl.class); - private final QbicSampleRepository qbicSampleRepository; - private final QbicSampleDataRepo sampleDataRepo; + private final SampleJpaRepository sampleJpaRepository; + private final SampleDataRepository sampleDataRepository; private final ProjectRepository projectRepository; + private SampleRepository selfProxy; + @Autowired - public SampleRepositoryImpl(QbicSampleRepository qbicSampleRepository, - QbicSampleDataRepo sampleDataRepo, ProjectRepository projectRepository) { - this.qbicSampleRepository = Objects.requireNonNull(qbicSampleRepository); - this.sampleDataRepo = Objects.requireNonNull(sampleDataRepo); + public SampleRepositoryImpl(SampleJpaRepository sampleJpaRepository, + SampleDataRepository sampleDataRepository, ProjectRepository projectRepository, + @Lazy SampleRepository selfProxy) { + this.selfProxy = selfProxy; + this.sampleJpaRepository = Objects.requireNonNull(sampleJpaRepository); + this.sampleDataRepository = Objects.requireNonNull(sampleDataRepository); this.projectRepository = Objects.requireNonNull(projectRepository); } + @Transactional(propagation = Propagation.REQUIRED) @Override - public Result, ResponseCode> addAll(Project project, + public Collection addAll(Project project, Collection samples) { String commaSeperatedSampleIds = buildCommaSeparatedSampleIds( samples.stream().map(Sample::sampleId).toList()); - List savedSamples; - try { - savedSamples = this.qbicSampleRepository.saveAll(samples); - } catch (Exception e) { - log.error("The samples:" + commaSeperatedSampleIds + "could not be saved", e); - return Result.fromError(ResponseCode.REGISTRATION_FAILED); - } + List savedSamples = this.sampleJpaRepository.saveAll(samples); try { - sampleDataRepo.addSamplesToProject(project, savedSamples); - } catch (Exception e) { - log.error("The samples:" + commaSeperatedSampleIds + "could not be stored in openBIS", e); - log.error("Removing samples from repository, as well."); - qbicSampleRepository.deleteAll(savedSamples); - return Result.fromError(ResponseCode.REGISTRATION_FAILED); + sampleDataRepository.addSamplesToProject(project, savedSamples); + } catch (RuntimeException e) { + throw new ApplicationException( + "The samples:" + commaSeperatedSampleIds + "could not be stored in openBIS", e); } - return Result.fromValue(savedSamples); + return savedSamples; } + @Transactional @Override - public Result, ResponseCode> addAll(ProjectId projectId, Collection samples) { + public Collection addAll(ProjectId projectId, Collection samples) { var projectQuery = projectRepository.find(projectId); - if (projectQuery.isPresent()) { - return addAll(projectQuery.get(), samples); + if (projectQuery.isEmpty()) { + throw new IllegalArgumentException("Project not found: " + projectId); } - return Result.fromError(ResponseCode.REGISTRATION_FAILED); + return selfProxy.addAll(projectQuery.get(), samples); } private String buildCommaSeparatedSampleIds(Collection sampleIds) { @@ -100,52 +97,48 @@ private String buildCommaSeparatedSampleIds(Collection sampleIds) { @Override public void deleteAll(Project project, Collection samples) { - List sampleCodes = qbicSampleRepository.findAllById(samples) + List sampleCodes = sampleJpaRepository.findAllById(samples) .stream().map(Sample::sampleCode).toList(); - this.qbicSampleRepository.deleteAllById(samples); + this.sampleJpaRepository.deleteAllById(samples); try { - sampleDataRepo.deleteAll(project.getProjectCode(), sampleCodes); + sampleDataRepository.deleteAll(project.getProjectCode(), sampleCodes); } catch (SampleNotDeletedException sampleNotDeletedException) { throw new ApplicationException("Could not delete " + buildCommaSeparatedSampleIds(samples), - sampleNotDeletedException, - ErrorCode.DATA_ATTACHED_TO_SAMPLES, ErrorParameters.empty()); + sampleNotDeletedException, ErrorCode.DATA_ATTACHED_TO_SAMPLES, ErrorParameters.empty()); + } catch (Exception e) { + throw new ApplicationException("Could not delete " + buildCommaSeparatedSampleIds(samples), + e); } } + @Transactional(readOnly = true) @Override public boolean isSampleRemovable(SampleId sampleId) { - SampleCode sampleCode = qbicSampleRepository.findById(sampleId).orElseThrow().sampleCode(); - return sampleDataRepo.canDeleteSample(sampleCode); + SampleCode sampleCode = sampleJpaRepository.findById(sampleId).orElseThrow().sampleCode(); + return sampleDataRepository.canDeleteSample(sampleCode); } + @Transactional(readOnly = true) @Override - public Result, SampleInformationService.ResponseCode> findSamplesByExperimentId( - ExperimentId experimentId) { + public Collection findSamplesByExperimentId(ExperimentId experimentId) { Objects.requireNonNull(experimentId); Collection samples; - try { - samples = qbicSampleRepository.findAllByExperimentId(experimentId); - } catch (Exception e) { - log.error( - "Retrieving Samples for experiment with id " + experimentId.value() + " failed: " + e, e); - return Result.fromError(SampleInformationService.ResponseCode.QUERY_FAILED); - } - return Result.fromValue(samples); + return sampleJpaRepository.findAllByExperimentId(experimentId); } @Override public List findSamplesByBatchId( BatchId batchId) { Objects.requireNonNull(batchId, "batchId must not be null"); - return qbicSampleRepository.findAllByAssignedBatch(batchId); + return sampleJpaRepository.findAllByAssignedBatch(batchId); } @Transactional @Override public void updateAll(Project project, Collection updatedSamples) { - qbicSampleRepository.saveAll(updatedSamples); - sampleDataRepo.updateAll(project, updatedSamples); + sampleJpaRepository.saveAll(updatedSamples); + sampleDataRepository.updateAll(project, updatedSamples); } @Transactional @@ -161,22 +154,22 @@ public void updateAll(ProjectId projectId, Collection updatedSamples) { @Override public List findSamplesBySampleId(List sampleId) { - return qbicSampleRepository.findAllById(sampleId); + return sampleJpaRepository.findAllById(sampleId); } @Override public Optional findSample(SampleCode sampleCode) { - return Optional.ofNullable(qbicSampleRepository.findBySampleCode(sampleCode)); + return Optional.ofNullable(sampleJpaRepository.findBySampleCode(sampleCode)); } @Override public Optional findSample(SampleId sampleId) { - return qbicSampleRepository.findById(sampleId); + return sampleJpaRepository.findById(sampleId); } @Override public long countSamplesWithExperimentId(ExperimentId experimentId) { - return qbicSampleRepository.countAllByExperimentId(experimentId); + return sampleJpaRepository.countAllByExperimentId(experimentId); } diff --git a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/openbis/OpenbisConnector.java b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/openbis/OpenbisConnector.java index 422dc0346..253e034ec 100644 --- a/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/openbis/OpenbisConnector.java +++ b/project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/sample/openbis/OpenbisConnector.java @@ -53,7 +53,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; +import life.qbic.application.commons.ApplicationException; import life.qbic.application.commons.SortOrder; import life.qbic.logging.api.Logger; import life.qbic.projectmanagement.application.DataRepoConnectionTester; @@ -62,6 +62,7 @@ import life.qbic.projectmanagement.application.dataset.RawDataService.RawDataDatasetInformation; import life.qbic.projectmanagement.application.sample.SampleIdCodeEntry; import life.qbic.projectmanagement.domain.model.measurement.MeasurementCode; +import life.qbic.projectmanagement.domain.model.measurement.MeasurementId; import life.qbic.projectmanagement.domain.model.measurement.NGSMeasurement; import life.qbic.projectmanagement.domain.model.measurement.ProteomicsMeasurement; import life.qbic.projectmanagement.domain.model.project.Project; @@ -69,7 +70,7 @@ import life.qbic.projectmanagement.domain.model.sample.SampleCode; import life.qbic.projectmanagement.infrastructure.experiment.measurement.MeasurementDataRepo; import life.qbic.projectmanagement.infrastructure.project.QbicProjectDataRepo; -import life.qbic.projectmanagement.infrastructure.sample.QbicSampleDataRepo; +import life.qbic.projectmanagement.infrastructure.sample.SampleDataRepository; import life.qbic.projectmanagement.infrastructure.sample.openbis.OpenbisSessionFactory.ApiV3; import life.qbic.projectmanagement.infrastructure.sample.openbis.OpenbisSessionFactory.OpenBisSession; import org.springframework.beans.factory.annotation.Value; @@ -81,7 +82,7 @@ * @since 1.0.0 */ @Component -public class OpenbisConnector implements QbicProjectDataRepo, QbicSampleDataRepo, +public class OpenbisConnector implements QbicProjectDataRepo, SampleDataRepository, MeasurementDataRepo, RawDataLookup, DataRepoConnectionTester { private static final Logger log = logger(OpenbisConnector.class); @@ -91,6 +92,8 @@ public class OpenbisConnector implements QbicProjectDataRepo, QbicSampleDataRepo private static final String DEFAULT_EXPERIMENT_TYPE = "Q_SAMPLE_BATCH"; private static final String EXTERNAL_ID_CODE = "Q_EXTERNAL_ID"; private static final String DEFAULT_DELETION_REASON = "Commanded by data manager app"; + private static final String NGS_MEASUREMENT_TYPE_CODE = "Q_NGS_MEASUREMENT"; + private static final String PROTEOMICS_MEASUREMENT_TYPE_CODE = "Q_PROTEOMICS_MEASUREMENT"; private final OpenbisSessionFactory sessionFactory; private final IApplicationServerApi applicationServer; private final IDataStoreServerApi datastoreServer; @@ -125,11 +128,11 @@ public static String getStringSizeLengthFile(long size) { float sizeTerra = sizeGb * sizeKb; if (size < sizeMb) { - return df.format(size / sizeKb) + " Kb"; + return df.format(size / sizeKb) + " KB"; } else if (size < sizeGb) { - return df.format(size / sizeMb) + " Mb"; + return df.format(size / sizeMb) + " MB"; } else if (size < sizeTerra) { - return df.format(size / sizeGb) + " Gb"; + return df.format(size / sizeGb) + " GB"; } return ""; @@ -244,7 +247,7 @@ private String findFreeExperimentCode(String projectCode) { private int getTrailingNumber(String input) { int lastNumberInt = 0; - Pattern lastIntPattern = Pattern.compile("[^0-9]+([0-9]+)$"); + Pattern lastIntPattern = Pattern.compile("\\D+(\\d+)$"); Matcher matcher = lastIntPattern.matcher(input); if (matcher.find()) { String someNumberStr = matcher.group(1); @@ -366,49 +369,32 @@ private List convertSamplesToSampleUpdates( return updatedSamples.stream().map(s -> createSampleUpdate(projectCode, s)).toList(); } - private SampleCreation prepareMeasurementSample(String sampleCode, - String measurementTypeCode, List parentIds, Map metadata) { - SampleCreation sampleCreation = new SampleCreation(); - sampleCreation.setCode(sampleCode); - sampleCreation.setParentIds(new ArrayList<>(parentIds)); - sampleCreation.setTypeId(new EntityTypePermId(measurementTypeCode)); - sampleCreation.setSpaceId(new SpacePermId(DEFAULT_SPACE_CODE)); - sampleCreation.setProperties(metadata); - return sampleCreation; - } - @Override public void addNGSMeasurement(NGSMeasurement measurement, List parentCodes) { - String TYPE_CODE = "Q_NGS_MEASUREMENT"; - Map metadata = new HashMap<>(); - metadata.put(EXTERNAL_ID_CODE, measurement.measurementId().value()); - try (OpenBisSession session = sessionFactory.getSession()) { - List parentIds = fetchSampleIdentifiers(session, - parentCodes.stream().map(SampleCode::code).toList()); - createOpenbisSamples(session, List.of( - prepareMeasurementSample(measurement.measurementCode().value(), TYPE_CODE, parentIds, - metadata))); - } + createMeasurementInOpenbis(parentCodes, NGS_MEASUREMENT_TYPE_CODE, measurement.measurementId(), + measurement.measurementCode()); } @Override public void saveAllNGS( Map> ngsMeasurementsMapping) { - String TYPE_CODE = "Q_NGS_MEASUREMENT"; - List objectsToCreate = new ArrayList<>(); try (OpenBisSession session = sessionFactory.getSession()) { - for (NGSMeasurement measurement : ngsMeasurementsMapping.keySet()) { - List parentCodes = ngsMeasurementsMapping.get(measurement).stream() - .map(entry -> entry.sampleCode().code()).collect(Collectors.toList()); - List parentIds = fetchSampleIdentifiers(session, parentCodes); - - Map metadata = new HashMap<>(); - metadata.put(EXTERNAL_ID_CODE, measurement.measurementId().value()); - - objectsToCreate.add(prepareMeasurementSample(measurement.measurementCode().value(), - TYPE_CODE, parentIds, metadata)); - } + var objectsToCreate = ngsMeasurementsMapping + .entrySet().stream() + .map( + entry -> { + NGSMeasurement measurement = entry.getKey(); + Collection sampleIdCodes = entry.getValue(); + + List parentCodes = sampleIdCodes.stream() + .map(sampleEntry -> sampleEntry.sampleCode().code()) + .toList(); + return prepareSampleCreation(NGS_MEASUREMENT_TYPE_CODE, session, parentCodes, + measurement.measurementId(), + measurement.measurementCode()); + } + ).toList(); createOpenbisSamples(session, objectsToCreate); } } @@ -416,40 +402,71 @@ public void saveAllNGS( @Override public void addProteomicsMeasurement(ProteomicsMeasurement measurement, List parentCodes) { - String TYPE_CODE = "Q_PROTEOMICS_MEASUREMENT"; + createMeasurementInOpenbis(parentCodes, PROTEOMICS_MEASUREMENT_TYPE_CODE, + measurement.measurementId(), + measurement.measurementCode()); + } + + private void createMeasurementInOpenbis(List parentCodes, String typeCode, + MeasurementId measurementId, MeasurementCode measurementCode) { Map metadata = new HashMap<>(); - metadata.put(EXTERNAL_ID_CODE, measurement.measurementId().value()); + metadata.put(EXTERNAL_ID_CODE, measurementId.value()); try (OpenBisSession session = sessionFactory.getSession()) { List parentIds = fetchSampleIdentifiers(session, parentCodes.stream().map(SampleCode::code).toList()); - createOpenbisSamples(session, Arrays.asList( - prepareMeasurementSample(measurement.measurementCode().value(), TYPE_CODE, parentIds, - metadata))); + String sampleCode = measurementCode.value(); + SampleCreation sampleCreation = new SampleCreation(); + sampleCreation.setCode(sampleCode); + sampleCreation.setParentIds(new ArrayList<>(parentIds)); + sampleCreation.setTypeId(new EntityTypePermId(typeCode)); + sampleCreation.setSpaceId(new SpacePermId(DEFAULT_SPACE_CODE)); + sampleCreation.setProperties(metadata); + createOpenbisSamples(session, List.of( + sampleCreation)); } } @Override public void saveAllProteomics( Map> proteomicsMeasurementsMapping) { - String TYPE_CODE = "Q_PROTEOMICS_MEASUREMENT"; - List objectsToCreate = new ArrayList<>(); try (OpenBisSession session = sessionFactory.getSession()) { - for (ProteomicsMeasurement measurement : proteomicsMeasurementsMapping.keySet()) { - List parentCodes = proteomicsMeasurementsMapping.get(measurement).stream() - .map(entry -> entry.sampleCode().code()).collect(Collectors.toList()); - List parentIds = fetchSampleIdentifiers(session, parentCodes); - - Map metadata = new HashMap<>(); - metadata.put(EXTERNAL_ID_CODE, measurement.measurementId().value()); - - objectsToCreate.add(prepareMeasurementSample(measurement.measurementCode().value(), - TYPE_CODE, parentIds, metadata)); - } + List objectsToCreate = proteomicsMeasurementsMapping + .entrySet().stream() + .map( + entry -> { + ProteomicsMeasurement measurement = entry.getKey(); + Collection sampleIdCodes = entry.getValue(); + + List parentCodes = sampleIdCodes.stream() + .map(sampleEntry -> sampleEntry.sampleCode().code()) + .toList(); + return prepareSampleCreation(PROTEOMICS_MEASUREMENT_TYPE_CODE, session, parentCodes, + measurement.measurementId(), + measurement.measurementCode()); + } + ).toList(); createOpenbisSamples(session, objectsToCreate); } } + private SampleCreation prepareSampleCreation(String typeCode, OpenBisSession session, + List parentCodes, MeasurementId measurementId, MeasurementCode measurementCode) { + List parentIds = fetchSampleIdentifiers(session, parentCodes); + + Map metadata = new HashMap<>(); + metadata.put(EXTERNAL_ID_CODE, measurementId.value()); + + String sampleCode = measurementCode.value(); + SampleCreation sampleCreation = new SampleCreation(); + sampleCreation.setCode(sampleCode); + sampleCreation.setParentIds(new ArrayList<>(parentIds)); + sampleCreation.setTypeId(new EntityTypePermId(typeCode)); + sampleCreation.setSpaceId(new SpacePermId(DEFAULT_SPACE_CODE)); + sampleCreation.setProperties(metadata); + return sampleCreation; + } + @Override public void deleteProteomicsMeasurements(List measurements) { deleteMeasurements(measurements.stream().map(ProteomicsMeasurement::measurementCode).toList()); @@ -462,7 +479,7 @@ public void deleteNGSMeasurements(List measurements) { private void deleteMeasurements(List measurementCodes) { try (OpenBisSession session = sessionFactory.getSession()) { - for(MeasurementCode code : measurementCodes) { + for (MeasurementCode code : measurementCodes) { String sampleCode = code.value(); // measurement has been deleted in JPA at this moment. We don't fail, but we keep data in // openbis that might have been registered between the check and deletion @@ -596,7 +613,6 @@ public List queryRawDataByMeasurementCodes(String fil searchCriteria.withAndOperator(); DataSetSearchCriteria filterCriteria = searchCriteria.withSubcriteria().withOrOperator(); filterCriteria.withSample().withCode().thatContains(filter); - //TODO other possibilities to filter by than the measured sample code? } try (OpenBisSession session = sessionFactory.getSession()) { @@ -674,7 +690,7 @@ private void handleOperations(OpenBisSession session, IOperation operation) { applicationServer.executeOperations(session.getToken(), operationOptions, executionOptions); } catch (Exception e) { log.error("Unexpected exception during openBIS operation.", e); - throw new RuntimeException(e); + throw new ApplicationException("Unexpected exception during openBIS operation.", e); } } @@ -697,13 +713,17 @@ public boolean projectExists(ProjectCode projectCode) { public void testDatastoreServer() { int major = datastoreServer.getMajorVersion(); int minor = datastoreServer.getMinorVersion(); - log.info("Successfully tested connection to openBIS datastore server version: %d.%d".formatted(major, minor)); + log.info( + "Successfully tested connection to openBIS datastore server version: %d.%d".formatted(major, + minor)); } @Override public void testApplicationServer() { try (OpenBisSession session = sessionFactory.getSession()) { - applicationServer.isSessionActive(session.getToken()); + if (!applicationServer.isSessionActive(session.getToken())) { + throw new ConnectionException("Could not connect to openBIS application server."); + } log.info("Successfully tested connection to openBIS application server."); } } @@ -711,6 +731,9 @@ public void testApplicationServer() { // Convenience RTE to describe connection issues static class ConnectionException extends RuntimeException { + public ConnectionException(String message) { + super(message); + } } static class MappingNotFoundException extends RuntimeException { diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/ProjectOverview.java b/project-management/src/main/java/life/qbic/projectmanagement/application/ProjectOverview.java index 5f0dc9021..e25e0af86 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/ProjectOverview.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/ProjectOverview.java @@ -47,10 +47,10 @@ public class ProjectOverview { private String projectResponsibleName; @Column(name = "amountNgsMeasurements") - private String ngsMeasurementCount; + private long ngsMeasurementCount; @Column(name = "amountPxpMeasurements") - private String pxpMeasurementCount; + private long pxpMeasurementCount; @Convert(converter = CollaboratorUserInfosConverter.class) @@ -89,11 +89,11 @@ public String projectResponsibleName() { return projectResponsibleName; } - public String ngsMeasurementCount() { + public long ngsMeasurementCount() { return ngsMeasurementCount; } - public String pxpMeasurementCount() { + public long pxpMeasurementCount() { return pxpMeasurementCount; } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableService.java b/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableService.java new file mode 100644 index 000000000..eb59d0c1c --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableService.java @@ -0,0 +1,191 @@ +package life.qbic.projectmanagement.application.confounding; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.springframework.security.access.prepost.PreAuthorize; + +/** + * This is the aggregate for confounding variables. You can + * {@link #listConfoundingVariablesForExperiment(String, ExperimentReference)} and for each + * variable list the existing levels with + * {@link #listLevelsForVariable(String, VariableReference)}. + *

+ * Further you can get the level of a specific variable for a specific sample with + * {@link #getVariableLevelForSample(String, SampleReference, VariableReference)} (ProjectId, ExperimentReference, SampleReference, + * VariableReference)} + *

+ * Apart from listing available information this service is the single point in the application + * where confounding variables may be modified. Please note: variable levels do not have an + * identifier as they are purely value objects and may be overwritten if changes occur. The service + * deletes the old level when a new level is set for a specific sample. + * + * @since 1.6.0 + */ +public interface ConfoundingVariableService { + + /** + * A reference to an experiment + * + * @param id the identifier of the experiment + */ + record ExperimentReference(String id) { + + } + + /** + * A reference to a sample + * @param id the identifier of the sample + */ + record SampleReference(String id) { + + } + + /** + * A reference to a variable + * @param id the identifier of the variable + */ + record VariableReference(long id) { + + } + + /** + * Information about a confounding variable + * @param id the identifier of the variable + * @param variableName the name of the variable + */ + record ConfoundingVariableInformation(VariableReference id, String variableName) { + + } + + /** + * A level of a variable on a sample + * @param variable the reference of the variable + * @param sample the reference of the sample + * @param level the value the variable has for the specific sample. + */ + record ConfoundingVariableLevel(VariableReference variable, SampleReference sample, + String level) { + + } + + /** + * List all confounding variables for a given experiment. + * @param projectId the project identifier for which to fetch data + * @param experiment the experiment for which to list the confounding variables + * @return a list of confounding variable information describing all confounding variables in the experiment. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List listConfoundingVariablesForExperiment(String projectId, + ExperimentReference experiment); + + /** + * List all confounding variables for a given experiment. + * + * @param projectId the project identifier for which to fetch data + * @param variables the variables for which to load information + * @return a list of confounding variable information describing all confounding variables in the + * experiment. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List loadInformationForVariables(String projectId, + List variables); + + + /** + * Get the level of a variable for a specific sample. {@link Optional#empty()} if no level is set + * for the specific variable. + * + * @param projectId the identifier of the project + * @param sampleReference the reference of the sample + * @param variableReference the reference to the variable + * @return the {@link ConfoundingVariableLevel} of the sample, {@link Optional#empty()} otherwise. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + Optional getVariableLevelForSample(String projectId, + SampleReference sampleReference, + VariableReference variableReference); + + /** + * Lists the levels of a confounding variable + * + * @param projectId the identifier of the project + * @param variableReference the reference of the variable + * @return a list of levels for the confounding variable. The list is empty if no levels exist. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List listLevelsForVariable(String projectId, + VariableReference variableReference); + + /** + * Lists the levels of a confounding variable + * + * @param projectId the identifier of the project + * @param variableReferences references of the variables + * @return a list of levels for the confounding variable. The list is empty if no levels exist. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List listLevelsForVariables(String projectId, + List variableReferences); + + /** + * Creates a confounding variable in an experiment. Information about the created confounding + * variable is returned. + * + * @param projectId the identifier of the project + * @param experiment a reference to the experiment in which to create the confounding variable. + * @param variableName the name of the confounding variable. + * @return information about the created confounding variable + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + ConfoundingVariableInformation createConfoundingVariable(String projectId, + ExperimentReference experiment, + String variableName); + + /** + * Set the level a sample has for a confounding variable. + *

+ * Overwrites an existing level of set sample in the variable, if present. + * + * @param projectId the identifier of the project + * @param experiment the experiment containing the variable + * @param sampleReference the sample for which to set the level + * @param variableReference the variable for which to set the level + * @param level the value of the level + * @return the created confounding variable level + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + ConfoundingVariableLevel setVariableLevelForSample(String projectId, + ExperimentReference experiment, SampleReference sampleReference, + VariableReference variableReference, String level); + + /** + * Set the level a sample has for a confounding variable. + *

+ * Overwrites an existing level of set sample in the variable, if present. + * + * @param projectId the identifier of the project + * @param experiment the experiment containing the variable + * @param sampleReference the sample for which to set the level + * @param variableReference the variable for which to set the level + * @param level the value of the level + * @return the created confounding variable level + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + List setVariableLevelsForSample(String projectId, + ExperimentReference experiment, SampleReference sampleReference, + Map levels); + + /** + * Deletes a confounding variable and all information about the variable. + *

+ * Please note: This will permanently delete all levels of the variable for all samples. + * @param projectId the identifier of the project + * @param experiment the experiment containing the confounding variable + * @param variableReference the confounding variable to delete. + */ + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteConfoundingVariable(String projectId, ExperimentReference experiment, + VariableReference variableReference); + +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableServiceImpl.java b/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableServiceImpl.java new file mode 100644 index 000000000..59b4ac17e --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/confounding/ConfoundingVariableServiceImpl.java @@ -0,0 +1,170 @@ +package life.qbic.projectmanagement.application.confounding; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import life.qbic.logging.api.Logger; +import life.qbic.logging.service.LoggerFactory; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableData; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableLevelData; +import life.qbic.projectmanagement.domain.repository.ConfoundingVariableLevelRepository; +import life.qbic.projectmanagement.domain.repository.ConfoundingVariableRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class ConfoundingVariableServiceImpl implements ConfoundingVariableService { + + private final ConfoundingVariableRepository variableRepository; + private final ConfoundingVariableLevelRepository levelRepository; + private static final Logger log = LoggerFactory.logger(ConfoundingVariableServiceImpl.class); + + public ConfoundingVariableServiceImpl(ConfoundingVariableRepository variableRepository, + ConfoundingVariableLevelRepository levelRepository) { + this.variableRepository = variableRepository; + this.levelRepository = levelRepository; + } + + @Override + @Transactional(readOnly = true) + public List listConfoundingVariablesForExperiment( + String projectId, ExperimentReference experiment) { + return variableRepository.findAll(projectId, experiment.id()) + .stream() + .map(it -> new ConfoundingVariableInformation(new VariableReference(it.getId()), + it.getName())) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public List loadInformationForVariables(String projectId, + List variables) { + return variableRepository.findAllById(projectId, + variables.stream().map(VariableReference::id).toList()).stream() + .map(data -> new ConfoundingVariableInformation(new VariableReference(data.getId()), + data.getName())) + .toList(); + } + + @Override + @Transactional + public ConfoundingVariableInformation createConfoundingVariable(String projectId, + ExperimentReference experiment, String variableName) { + ConfoundingVariableData confoundingVariableData = new ConfoundingVariableData(null, + experiment.id(), variableName); + ConfoundingVariableData savedVariable = variableRepository.save(projectId, + confoundingVariableData); + return new ConfoundingVariableInformation(new VariableReference(savedVariable.getId()), + savedVariable.getName()); + } + + @Override + @Transactional + public ConfoundingVariableLevel setVariableLevelForSample(String projectId, + ExperimentReference experiment, SampleReference sampleReference, + VariableReference variableReference, + String level) { + ConfoundingVariableData existingVariable = variableRepository.findById(projectId, + variableReference.id()).orElseThrow(); + log.debug( + "Adding level %s for variable %s to sample %s".formatted(level, existingVariable.getName(), + sampleReference.id())); + Optional existingLevel = levelRepository.findVariableLevelOfSample( + projectId, sampleReference.id(), + variableReference.id()); + var constructedLevel = existingLevel.map( + it -> new ConfoundingVariableLevelData( + it.getId(), + it.getVariableId(), + it.getSampleId(), + level + ) + ).orElse(new ConfoundingVariableLevelData(null, existingVariable.getId(), + sampleReference.id(), level)); + ConfoundingVariableLevelData savedLevelData = levelRepository.save(projectId, constructedLevel); + return new ConfoundingVariableLevel(new VariableReference(savedLevelData.getVariableId()), + new SampleReference(savedLevelData.getSampleId()), savedLevelData.getValue()); + } + + @Override + public List setVariableLevelsForSample(String projectId, + ExperimentReference experiment, SampleReference sampleReference, + Map levels) { + List variableIds = levels.keySet().stream().map(VariableReference::id).toList(); + List savedLevels = new ArrayList<>(); + if (!variableRepository.existsAllById(projectId, variableIds)) { + throw new IllegalArgumentException( + "Not all variables exist in the database. Provided variables: " + levels.keySet()); + } + for (Entry levelEntry : levels.entrySet()) { + log.debug("Adding level %s for variable %s to sample %s".formatted(levelEntry.getValue(), + levelEntry.getKey(), sampleReference.id())); + Optional existingLevel = levelRepository.findVariableLevelOfSample( + projectId, sampleReference.id(), levelEntry.getKey().id()); + var constructedLevel = existingLevel.map( + it -> new ConfoundingVariableLevelData(it.getId(), it.getVariableId(), it.getSampleId(), + levelEntry.getValue())).orElse( + new ConfoundingVariableLevelData(null, levelEntry.getKey().id(), sampleReference.id(), + levelEntry.getValue())); + ConfoundingVariableLevelData savedLevelData = levelRepository.save(projectId, + constructedLevel); + savedLevels.add( + new ConfoundingVariableLevel(new VariableReference(savedLevelData.getVariableId()), + new SampleReference(savedLevelData.getSampleId()), savedLevelData.getValue())); + } + return savedLevels; + } + + @Override + @Transactional(readOnly = true) + public Optional getVariableLevelForSample(String projectId, + SampleReference sampleReference, + VariableReference variableReference) { + return levelRepository.findVariableLevelOfSample(projectId, sampleReference.id(), + variableReference.id()).map( + data -> new ConfoundingVariableLevel(new VariableReference(data.getVariableId()), + new SampleReference(data.getSampleId()), + data.getValue())); + } + + @Override + @Transactional(readOnly = true) + public List listLevelsForVariable(String projectId, + VariableReference variableReference) { + return levelRepository.findAllForVariable(projectId, variableReference.id()).stream() + .map(data -> new ConfoundingVariableLevel( + new VariableReference(data.getVariableId()), + new SampleReference(data.getSampleId()), + data.getValue() + )).toList(); + } + + @Override + @Transactional(readOnly = true) + public List listLevelsForVariables(String projectId, + List variableReferences) { + return levelRepository.findAllForVariables(projectId, + variableReferences.stream().map(VariableReference::id).toList()).stream() + .map(data -> new ConfoundingVariableLevel( + new VariableReference(data.getVariableId()), + new SampleReference(data.getSampleId()), + data.getValue() + )).toList(); + } + + @Override + @Transactional + public void deleteConfoundingVariable(String projectId, + ExperimentReference experiment, + VariableReference variableReference) { + Optional optionalVariable = variableRepository.findById(projectId, + variableReference.id()); + optionalVariable.ifPresent(variable -> { + levelRepository.deleteAllForVariable(projectId, variableReference.id()); + variableRepository.deleteById(projectId, variableReference.id()); + }); + } +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleInformationService.java b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleInformationService.java index dd3f160d6..8f4cbc162 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleInformationService.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleInformationService.java @@ -53,7 +53,12 @@ public boolean hasSamples(ExperimentId experimentId) { public Result, ResponseCode> retrieveSamplesForExperiment( ExperimentId experimentId) { Objects.requireNonNull(experimentId, "experiment id must not be null"); - return sampleRepository.findSamplesByExperimentId(experimentId); + try { + return Result.fromValue(sampleRepository.findSamplesByExperimentId(experimentId)); + } catch (RuntimeException e) { + log.error(e.getMessage(), e); + return Result.fromError(ResponseCode.QUERY_FAILED); + } } public List retrieveSamplePreviewsForExperiment(ExperimentId experimentId) { diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleMetadata.java b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleMetadata.java index 9cd1cde34..5a8d79eec 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleMetadata.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleMetadata.java @@ -1,6 +1,8 @@ package life.qbic.projectmanagement.application.sample; +import java.util.Map; import java.util.Optional; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation; import life.qbic.projectmanagement.domain.model.OntologyTerm; import life.qbic.projectmanagement.domain.model.sample.AnalysisMethod; import life.qbic.projectmanagement.domain.model.sample.SampleId; @@ -23,6 +25,7 @@ public record SampleMetadata( OntologyTerm specimen, OntologyTerm analyte, String comment, + Map confoundingVariables, String experimentId ) { @@ -34,9 +37,11 @@ public static SampleMetadata createNew(String sampleName, OntologyTerm specimen, OntologyTerm analyte, String comment, + Map confoundingVariables, String experimentId) { return new SampleMetadata(null, "", sampleName, analysisToBePerformed, biologicalReplicate, - experimentalGroupId, species, specimen, analyte, comment, experimentId); + experimentalGroupId, species, specimen, analyte, comment, confoundingVariables, + experimentId); } public static SampleMetadata createUpdate(SampleId sampleId, @@ -49,12 +54,25 @@ public static SampleMetadata createUpdate(SampleId sampleId, OntologyTerm specimen, OntologyTerm analyte, String comment, + Map confoundingVariables, String experimentId) { return new SampleMetadata(sampleId, sampleCode, sampleName, analysisToBePerformed, biologicalReplicate, experimentalGroupId, species, specimen, analyte, comment, + confoundingVariables, experimentId); } + public static SampleMetadata fromNewToExisting(SampleId id, SampleMetadata sampleMetadata) { + return new SampleMetadata( + id, sampleMetadata.sampleCode, sampleMetadata.sampleName, + sampleMetadata.analysisToBePerformed, sampleMetadata.biologicalReplicate, + sampleMetadata.experimentalGroupId, sampleMetadata.species, sampleMetadata.specimen, + sampleMetadata.analyte, sampleMetadata.comment, + sampleMetadata.confoundingVariables, + sampleMetadata.experimentId + ); + } + public Optional getSampleId() { return Optional.ofNullable(sampleId); } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleRegistrationServiceV2.java b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleRegistrationServiceV2.java index dc56cb7bf..e7d09748e 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleRegistrationServiceV2.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleRegistrationServiceV2.java @@ -2,15 +2,22 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Collectors; import life.qbic.projectmanagement.application.DeletionService; import life.qbic.projectmanagement.application.api.SampleCodeService; import life.qbic.projectmanagement.application.batch.BatchRegistrationService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.SampleReference; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.VariableReference; import life.qbic.projectmanagement.domain.model.batch.Batch; import life.qbic.projectmanagement.domain.model.batch.BatchId; import life.qbic.projectmanagement.domain.model.experiment.ExperimentId; @@ -26,6 +33,8 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; /** * @@ -42,32 +51,46 @@ public class SampleRegistrationServiceV2 { private final BatchRepository batchRepository; private final SampleCodeService sampleCodeService; private final DeletionService deletionService; + private final ConfoundingVariableService confoundingVariableService; @Autowired public SampleRegistrationServiceV2(BatchRegistrationService batchRegistrationService, SampleRepository sampleRepository, BatchRepository batchRepository, SampleCodeService sampleCodeService, - DeletionService deletionService) { + DeletionService deletionService, ConfoundingVariableService confoundingVariableService) { this.batchRegistrationService = Objects.requireNonNull(batchRegistrationService); this.sampleRepository = Objects.requireNonNull(sampleRepository); this.batchRepository = Objects.requireNonNull(batchRepository); this.sampleCodeService = Objects.requireNonNull(sampleCodeService); this.deletionService = Objects.requireNonNull(deletionService); + this.confoundingVariableService = confoundingVariableService; } @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") @Async + @Transactional(propagation = Propagation.REQUIRES_NEW) public CompletableFuture registerSamples(Collection sampleMetadata, - ProjectId projectId, String batchLabel, boolean batchIsPilot, ExperimentId experimentId) + ProjectId projectId, String batchLabel, boolean batchIsPilot, ExperimentReference experiment) throws RegistrationException { var result = batchRegistrationService.registerBatch(batchLabel, batchIsPilot, projectId, - experimentId); + ExperimentId.parse(experiment.id())); if (result.isError()) { throw new RegistrationException("Batch registration failed"); } var batchId = result.getValue(); try { - var sampleIds = registerSamples(sampleMetadata, batchId, projectId); + var registeredSamples = registerSamples(sampleMetadata, batchId, projectId); + for (Entry registeredSample : registeredSamples.entrySet()) { + Map levels = new HashMap<>(); + registeredSample.getValue().confoundingVariables().forEach((k, v) -> levels.put(k.id(), v)); + SampleReference sampleReference = new SampleReference( + registeredSample.getKey().sampleId().value()); + confoundingVariableService.setVariableLevelsForSample(projectId.value(), + experiment, + sampleReference, levels); + } + Set sampleIds = registeredSamples.keySet().stream().map(Sample::sampleId).collect( + Collectors.toSet()); batchRegistrationService.addSamplesToBatch(sampleIds, batchId, projectId); } catch (RuntimeException e) { rollbackSampleRegistration(batchId); @@ -81,12 +104,14 @@ public CompletableFuture registerSamples(Collection sample @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") @Async + @Transactional(propagation = Propagation.REQUIRES_NEW) public CompletableFuture updateSamples( Collection sampleMetadata, ProjectId projectId, BatchId batchId, String batchLabel, - boolean isPilot) + boolean isPilot, + ExperimentReference experiment) throws RegistrationException { Batch batch = batchRepository.find(batchId) @@ -101,14 +126,25 @@ public CompletableFuture updateSamples( var sampleBySampleCode = samples.stream() .collect(Collectors.toMap(sample -> sample.sampleCode().code(), Function.identity())); var updatedSamples = updateSamples(sampleBySampleCode, sampleMetadata); - sampleRepository.updateAll(projectId, updatedSamples); + sampleRepository.updateAll(projectId, updatedSamples.keySet()); + + for (Entry updatedSample : updatedSamples.entrySet()) { + Map levels = new HashMap<>(); + + updatedSample.getValue().confoundingVariables().forEach((k, v) -> levels.put(k.id(), v)); + SampleReference sampleReference = new SampleReference( + updatedSample.getKey().sampleId().value()); + confoundingVariableService.setVariableLevelsForSample(projectId.value(), experiment, + sampleReference, levels); + } + batchRegistrationService.addSamplesToBatch(sampleIds, batchId, projectId); batchRepository.update(batch); return CompletableFuture.completedFuture(null); } - private List updateSamples(Map samples, + private Map updateSamples(Map samples, Collection sampleMetadataList) { - var updatedSamples = new ArrayList(); + var samplesToUpdate = new HashMap(); for (SampleMetadata sampleMetadata : sampleMetadataList) { var sampleForUpdate = samples.get(sampleMetadata.sampleCode()); sampleForUpdate.setLabel(sampleMetadata.sampleName()); @@ -119,24 +155,43 @@ private List updateSamples(Map samples, sampleForUpdate.setBiologicalReplicate(sampleMetadata.biologicalReplicate()); sampleForUpdate.setExperimentalGroupId(sampleMetadata.experimentalGroupId()); sampleForUpdate.setComment(sampleMetadata.comment()); - updatedSamples.add(sampleForUpdate); + samplesToUpdate.put(sampleForUpdate, SampleMetadata.createUpdate( + sampleMetadata.sampleId(), + sampleMetadata.sampleCode(), + sampleMetadata.sampleName(), + sampleMetadata.analysisToBePerformed(), + sampleMetadata.biologicalReplicate(), + sampleMetadata.experimentalGroupId(), + sampleMetadata.species(), + sampleMetadata.specimen(), + sampleMetadata.analyte(), + sampleMetadata.comment(), + sampleMetadata.confoundingVariables(), + sampleMetadata.experimentId() + )); } - return updatedSamples; + return samplesToUpdate; } - private Collection registerSamples(Collection sampleMetadata, + private Map registerSamples(Collection sampleMetadata, BatchId batchId, ProjectId projectId) throws RegistrationException { - var samplesToRegister = new ArrayList(); + var samplesToRegister = new HashMap(); var sampleCodes = generateSampleCodes(sampleMetadata.size(), projectId).iterator(); - for (SampleMetadata sample : sampleMetadata) { - samplesToRegister.add(buildSample(sample, batchId, sampleCodes.next())); + for (SampleMetadata metadata : sampleMetadata) { + Sample sample = buildSample(metadata, batchId, sampleCodes.next()); + samplesToRegister.put(sample, metadata); } - return sampleRepository.addAll(projectId, samplesToRegister) - .valueOrElseThrow(e -> new RegistrationException("Could not register samples: " + e.name())) - .stream().map(Sample::sampleId) - .toList(); + var registeredSamples = new HashMap(); + List addedSamples = sampleRepository.addAll(projectId, samplesToRegister.keySet()) + .stream().toList(); + for (Sample addedSample : addedSamples) { + SampleMetadata metadata = samplesToRegister.get(addedSample); + registeredSamples.put(addedSample, + SampleMetadata.fromNewToExisting(addedSample.sampleId(), metadata)); + } + return registeredSamples; } private Sample buildSample(SampleMetadata sample, BatchId batchId, SampleCode sampleCode) { @@ -166,11 +221,4 @@ public RegistrationException(String message) { } } - public static class UnknownSampleException extends RuntimeException { - - public UnknownSampleException(String message) { - super(message); - } - } - } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidation.java b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidation.java index 15de1c854..b80936b28 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidation.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidation.java @@ -2,13 +2,21 @@ import static java.util.Objects.isNull; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import life.qbic.projectmanagement.application.ValidationResult; import life.qbic.projectmanagement.application.ValidationResultWithPayload; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference; import life.qbic.projectmanagement.application.experiment.ExperimentInformationService; import life.qbic.projectmanagement.application.ontology.SpeciesLookupService; import life.qbic.projectmanagement.application.ontology.TerminologyService; @@ -39,15 +47,18 @@ public class SampleValidation { private final TerminologyService terminologyService; private final SpeciesLookupService speciesLookupService; + private final ConfoundingVariableService confoundingVariableService; @Autowired public SampleValidation(SampleInformationService sampleInformationService, ExperimentInformationService experimentInformationService, - TerminologyService terminologyService, SpeciesLookupService speciesLookupService) { + TerminologyService terminologyService, SpeciesLookupService speciesLookupService, + ConfoundingVariableService confoundingVariableService) { this.sampleInformationService = Objects.requireNonNull(sampleInformationService); this.experimentInformationService = Objects.requireNonNull(experimentInformationService); this.terminologyService = Objects.requireNonNull(terminologyService); this.speciesLookupService = Objects.requireNonNull(speciesLookupService); + this.confoundingVariableService = confoundingVariableService; } /** @@ -96,6 +107,7 @@ public ValidationResultWithPayload validateNewSample(String samp String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { @@ -116,8 +128,9 @@ public ValidationResultWithPayload validateNewSample(String samp analyte, analysisMethod, comment, - experimentId, - experimentalGroupLookupTable); + experimentalGroupLookupTable, + confoundingVariables, + experimentId, projectId); } private ValidationResultWithPayload validateForNewSample( @@ -129,8 +142,10 @@ private ValidationResultWithPayload validateForNewSample( String analyte, String analysisMethod, String comment, + Map experimentalGroupLookupTable, + Map confoundingVariables, String experimentId, - Map experimentalGroupLookupTable) { + String projectId) { var sampleNameValidation = validateSampleName(sampleName); var experimentalGroupValidation = validateExperimentalGroupForCondition(condition, @@ -139,6 +154,8 @@ private ValidationResultWithPayload validateForNewSample( var speciesValidation = validateSpecies(species); var specimenValidation = validateSpecimen(specimen); var analyteValidation = validateAnalyte(analyte); + var confoundingVariableValidation = validateConfoundingVariableLevels(confoundingVariables, + new ExperimentReference(experimentId), projectId); ValidationResult combinedValidationResult = ValidationResult.successful() .combine(sampleNameValidation.validationResult()) @@ -146,7 +163,9 @@ private ValidationResultWithPayload validateForNewSample( .combine(analysisMethodValidation.validationResult()) .combine(speciesValidation.validationResult()) .combine(specimenValidation.validationResult()) - .combine(analyteValidation.validationResult()); + .combine(analyteValidation.validationResult()) + .combine(confoundingVariableValidation.validationResult()); + var metadata = combinedValidationResult.containsFailures() ? null : SampleMetadata.createNew( @@ -158,6 +177,7 @@ private ValidationResultWithPayload validateForNewSample( specimenValidation.payload(), analyteValidation.payload(), comment, + confoundingVariableValidation.payload(), experimentId); return new ValidationResultWithPayload<>(combinedValidationResult, metadata); } @@ -275,6 +295,28 @@ private ValidationResultWithPayload validateSampleIdForSampleCode(Stri return new ValidationResultWithPayload<>(ValidationResult.successful(), sampleId); } + private ValidationResultWithPayload> validateConfoundingVariableLevels( + Map confoundingVariables, ExperimentReference experimentId, + String projectId) { + //can produce: Unknown confounding variables `X, Y, Z` + List confoundingVariableInformation = confoundingVariableService.listConfoundingVariablesForExperiment( + projectId, experimentId); + Set unknownVariables = new HashSet<>(); + var res = new HashMap(); + for (Entry confoundingLevel : confoundingVariables.entrySet()) { + Optional existingVariable = confoundingVariableInformation.stream() + .filter(it -> (it.variableName()).equals(confoundingLevel.getKey())).findAny(); + existingVariable.ifPresentOrElse( + vari -> res.put(vari, confoundingLevel.getValue()), + () -> unknownVariables.add(confoundingLevel.getKey())); + } + return new ValidationResultWithPayload<>( + ValidationResult.withFailures( + unknownVariables.stream().map("Unknown confounding variable '%s'"::formatted).toList()), + res); + } + + /** * Validates the metadata for a sample that has previously been registered. A registered sample * has a sample code (sample id to the user) and an internal technical sample id. @@ -286,14 +328,15 @@ private ValidationResultWithPayload validateSampleIdForSampleCode(Stri * {@link SampleValidation#validateNewSample(String, String, String, String, String, String, * String, String, String, String)} call. * - * @param sampleCode the sample code of the sample, known as sample id to the user - * @param condition the condition the sample was collected from - * @param species the species the sample was taken from - * @param specimen the specimen of the sample - * @param analyte the analyte that was extracted from the specimen - * @param analysisMethod the method applied on the analyte - * @param experimentId the experiment the sample belongs to - * @param projectId the project the sample belongs to + * @param sampleCode the sample code of the sample, known as sample id to the user + * @param condition the condition the sample was collected from + * @param species the species the sample was taken from + * @param specimen the specimen of the sample + * @param analyte the analyte that was extracted from the specimen + * @param analysisMethod the method applied on the analyte + * @param confoundingVariables + * @param experimentId the experiment the sample belongs to + * @param projectId the project the sample belongs to * @return a {@link ValidationResult} with detailed information about the validation * @since 1.5.0 */ @@ -306,6 +349,7 @@ public ValidationResultWithPayload validateExistingSample(String String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { @@ -327,6 +371,8 @@ public ValidationResultWithPayload validateExistingSample(String var speciesValidation = validateSpecies(species); var specimenValidation = validateSpecimen(specimen); var analyteValidation = validateAnalyte(analyte); + var confoundingVariableValidation = validateConfoundingVariableLevels(confoundingVariables, + new ExperimentReference(experimentId), projectId); ValidationResult combinedValidationResult = ValidationResult.successful() .combine(sampleIdValidation.validationResult()) @@ -335,7 +381,9 @@ public ValidationResultWithPayload validateExistingSample(String .combine(analysisMethodValidation.validationResult()) .combine(speciesValidation.validationResult()) .combine(specimenValidation.validationResult()) - .combine(analyteValidation.validationResult()); + .combine(analyteValidation.validationResult()) + .combine(confoundingVariableValidation.validationResult()); + var metadata = combinedValidationResult.containsFailures() ? null : SampleMetadata.createUpdate( @@ -349,6 +397,7 @@ public ValidationResultWithPayload validateExistingSample(String specimenValidation.payload(), analyteValidation.payload(), comment, + confoundingVariableValidation.payload(), experimentId); return new ValidationResultWithPayload<>(combinedValidationResult, metadata); } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidationService.java b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidationService.java index 2fc8ba172..c7a51a1c7 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidationService.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/application/sample/SampleValidationService.java @@ -1,5 +1,6 @@ package life.qbic.projectmanagement.application.sample; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import life.qbic.projectmanagement.application.ValidationResultWithPayload; @@ -35,6 +36,7 @@ public ValidationResultWithPayload validateNewSample( String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { return sampleValidation.validateNewSample(sampleName, @@ -45,6 +47,7 @@ public ValidationResultWithPayload validateNewSample( analyte, analysisMethod, comment, + confoundingVariables, experimentId, projectId); } @@ -60,6 +63,7 @@ public ValidationResultWithPayload validateExistingSample( String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { return sampleValidation.validateExistingSample(sampleCode, @@ -71,6 +75,7 @@ public ValidationResultWithPayload validateExistingSample( analyte, analysisMethod, comment, + confoundingVariables, experimentId, projectId); } @@ -86,6 +91,7 @@ public CompletableFuture> validateNe String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { return CompletableFuture.completedFuture( @@ -97,6 +103,7 @@ public CompletableFuture> validateNe analyte, analysisMethod, comment, + confoundingVariables, experimentId, projectId)); } @@ -113,6 +120,7 @@ public CompletableFuture> validateEx String analyte, String analysisMethod, String comment, + Map confoundingVariables, String experimentId, String projectId) { return CompletableFuture.completedFuture( @@ -125,6 +133,7 @@ public CompletableFuture> validateEx analyte, analysisMethod, comment, + confoundingVariables, experimentId, projectId)); } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/diagrams/confounding-variables.md b/project-management/src/main/java/life/qbic/projectmanagement/diagrams/confounding-variables.md new file mode 100644 index 000000000..2bd346ec2 --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/diagrams/confounding-variables.md @@ -0,0 +1,51 @@ +## ER Diagram + +```mermaid + erDiagram + CVar["Confounding Variable"] { + Identifier variableId + String name + } + CVarLevel["Confounding Variable Level"] { + Reference sampleId + Reference variableId + String value + } + SAMPLE["Sample"] { + Identifier sampleId + String sampleName + } + EXPERIMENT["Experiment"] { + } + EXPERIMENT one--zero or more SAMPLE : contains +EXPERIMENT one--zero or more CVar: contains +CVarLevel 1+--1 CVar: describes +CVarLevel zero or more--one SAMPLE: describes +``` + +## Sequence + +```mermaid +--- + look: handDrawn + theme: neutral +--- +sequenceDiagram + actor UI as User Inferface + participant SERVICE as Confounding Variable Service + participant VAR_REPO as Variable Repository + participant LEVEL_REPO as Variable Level Repository + participant EXP as Experiment Repository + UI ->>+ SERVICE: add variable X with level Y to sample S + opt "Variable does not exists" + SERVICE ->>+ VAR_REPO: create variable + VAR_REPO -->>- SERVICE: variable id + SERVICE ->>+ EXP: get experiment + EXP -->>- SERVICE: experiment + SERVICE ->>+ EXP: add confounding variable + EXP -->>- SERVICE: Success + end + SERVICE ->>+ LEVEL_REPO: add variable level + LEVEL_REPO -->>- SERVICE: variable level information + SERVICE ->>- UI: variable level information +``` diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableData.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableData.java new file mode 100644 index 000000000..985e0a631 --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableData.java @@ -0,0 +1,86 @@ +package life.qbic.projectmanagement.domain.model.confounding.jpa; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.Objects; + +/** + * TODO! + * short description + * + *

detailed description

+ * + * @since + */ +@Entity +@Table(name = "experiments_datamanager_confounding_variables") +public class ConfoundingVariableData { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @Column(name = "experimentId", nullable = false) + private String experimentId; + @Column(name = "name", nullable = false) + private String name; + + + protected ConfoundingVariableData() { + } + + public ConfoundingVariableData(Long id, String experimentId, String name) { + this.id = id; + this.experimentId = experimentId; + this.name = name; + } + + public Long getId() { + return id; + } + + protected void setId(Long id) { + this.id = id; + } + + public String getExperimentId() { + return experimentId; + } + + protected void setExperimentId(String experimentId) { + this.experimentId = experimentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ConfoundingVariableData that)) { + return false; + } + + return Objects.equals(id, that.id) && Objects.equals(experimentId, that.experimentId) + && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(experimentId); + result = 31 * result + Objects.hashCode(name); + return result; + } +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableLevelData.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableLevelData.java new file mode 100644 index 000000000..3e35defe6 --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/confounding/jpa/ConfoundingVariableLevelData.java @@ -0,0 +1,74 @@ +package life.qbic.projectmanagement.domain.model.confounding.jpa; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * TODO! + * short description + * + *

detailed description

+ * + * @since + */ +@Entity +@Table(name = "confounding_variable_levels") +public class ConfoundingVariableLevelData { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @Column(name = "variableId", nullable = false) + private long variableId; + @Column(name = "sampleId", nullable = false) + private String sampleId; + @Column(name = "value") + private String value; + + protected ConfoundingVariableLevelData() { + } + + public ConfoundingVariableLevelData(Long id, long variableId, String sampleId, String value) { + this.id = id; + this.variableId = variableId; + this.sampleId = sampleId; + this.value = value; + } + + public long getId() { + return id; + } + + protected void setId(long id) { + this.id = id; + } + + public long getVariableId() { + return variableId; + } + + protected void setVariableId(long variableId) { + this.variableId = variableId; + } + + public String getSampleId() { + return sampleId; + } + + protected void setSampleId(String sampleId) { + this.sampleId = sampleId; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/model/sample/Sample.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/sample/Sample.java index 798a57414..35d70f51c 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/domain/model/sample/Sample.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/model/sample/Sample.java @@ -200,4 +200,17 @@ private void emitCreatedEvent() { LocalDomainEventDispatcher.instance().dispatch(createdEvent); } + @Override + public final boolean equals(Object o) { + if (!(o instanceof Sample sample)) { + return false; + } + + return Objects.equals(id, sample.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } } diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableLevelRepository.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableLevelRepository.java new file mode 100644 index 000000000..f22c4b7cf --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableLevelRepository.java @@ -0,0 +1,50 @@ +package life.qbic.projectmanagement.domain.repository; + +import java.util.List; +import java.util.Optional; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableLevelData; +import org.springframework.security.access.prepost.PreAuthorize; + +/** + * TODO! + * short description + * + *

detailed description

+ * + * @since + */ +public interface ConfoundingVariableLevelRepository { + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List findAllForSample(String projectId, String sampleId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List findAllForVariable(String projectId, long variableId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + Optional findVariableLevelOfSample(String projectId, + String sampleId, long variableId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List findAllForVariables(String projectId, List variableIds); + + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + S save(String projectId, S entity); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + Optional findById(String projectId, Long aLong); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + long countLevelsOfVariable(String projectId, long variableId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteById(String projectId, Long aLong); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteAllForSample(String projectId, String sampleId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteAllForVariable(String projectId, long variableId); + +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableRepository.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableRepository.java new file mode 100644 index 000000000..b30b095e0 --- /dev/null +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/ConfoundingVariableRepository.java @@ -0,0 +1,47 @@ +package life.qbic.projectmanagement.domain.repository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import life.qbic.projectmanagement.domain.model.confounding.jpa.ConfoundingVariableData; +import org.springframework.security.access.prepost.PreAuthorize; + +/** + * TODO! + * short description + * + *

detailed description

+ * + * @since + */ +public interface ConfoundingVariableRepository { + + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + List saveAll(String projectId, Iterable entities); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List findAll(String projectId, String experimentId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + List findAllById(String projectId, Iterable longs); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + boolean existsAllById(String projectId, Collection longs); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + S save(String projectId, S entity); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + Optional findById(String projectId, Long aLong); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'READ')") + long countVariablesOfExperiment(String projectId, String experimentId); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteById(String projectId, Long aLong); + + @PreAuthorize("hasPermission(#projectId, 'life.qbic.projectmanagement.domain.model.project.Project', 'WRITE')") + void deleteAllById(String projectId, Iterable longs); + +} diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/SampleRepository.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/SampleRepository.java index 20f799940..d3c64d350 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/SampleRepository.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/repository/SampleRepository.java @@ -3,8 +3,6 @@ import java.util.Collection; import java.util.List; import java.util.Optional; -import life.qbic.application.commons.Result; -import life.qbic.projectmanagement.application.sample.SampleInformationService; import life.qbic.projectmanagement.domain.model.batch.BatchId; import life.qbic.projectmanagement.domain.model.experiment.ExperimentId; import life.qbic.projectmanagement.domain.model.project.Project; @@ -12,7 +10,6 @@ import life.qbic.projectmanagement.domain.model.sample.Sample; import life.qbic.projectmanagement.domain.model.sample.SampleCode; import life.qbic.projectmanagement.domain.model.sample.SampleId; -import life.qbic.projectmanagement.domain.service.SampleDomainService.ResponseCode; /** * Sample data storage interface @@ -30,14 +27,13 @@ public interface SampleRepository { * @param samples a batch of samples to save * @since 1.0.0 */ - Result, ResponseCode> addAll(Project project, Collection samples); + Collection addAll(Project project, Collection samples); - Result, ResponseCode> addAll(ProjectId projectId, Collection samples); + Collection addAll(ProjectId projectId, Collection samples); void deleteAll(Project project, Collection sampleIds); - Result, SampleInformationService.ResponseCode> findSamplesByExperimentId( - ExperimentId experimentId); + Collection findSamplesByExperimentId(ExperimentId experimentId); List findSamplesByBatchId(BatchId batchId); diff --git a/project-management/src/main/java/life/qbic/projectmanagement/domain/service/SampleDomainService.java b/project-management/src/main/java/life/qbic/projectmanagement/domain/service/SampleDomainService.java index 1dfac717b..32619d6fd 100644 --- a/project-management/src/main/java/life/qbic/projectmanagement/domain/service/SampleDomainService.java +++ b/project-management/src/main/java/life/qbic/projectmanagement/domain/service/SampleDomainService.java @@ -10,6 +10,8 @@ import life.qbic.domain.concepts.DomainEventDispatcher; import life.qbic.domain.concepts.DomainEventSubscriber; import life.qbic.domain.concepts.LocalDomainEventDispatcher; +import life.qbic.logging.api.Logger; +import life.qbic.logging.service.LoggerFactory; import life.qbic.projectmanagement.application.batch.SampleUpdateRequest; import life.qbic.projectmanagement.domain.model.batch.BatchId; import life.qbic.projectmanagement.domain.model.project.Project; @@ -23,6 +25,7 @@ import life.qbic.projectmanagement.domain.model.sample.event.SampleDeleted; import life.qbic.projectmanagement.domain.model.sample.event.SampleRegistered; import life.qbic.projectmanagement.domain.model.sample.event.SampleUpdated; +import life.qbic.projectmanagement.domain.repository.ConfoundingVariableLevelRepository; import life.qbic.projectmanagement.domain.repository.SampleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -39,10 +42,14 @@ public class SampleDomainService { private final SampleRepository sampleRepository; + private final Logger log = LoggerFactory.logger(SampleDomainService.class); + private final ConfoundingVariableLevelRepository confoundingVariableLevelRepository; @Autowired - public SampleDomainService(SampleRepository sampleRepository) { + public SampleDomainService(SampleRepository sampleRepository, + ConfoundingVariableLevelRepository confoundingVariableLevelRepository) { this.sampleRepository = Objects.requireNonNull(sampleRepository); + this.confoundingVariableLevelRepository = confoundingVariableLevelRepository; } public Result, ResponseCode> registerSamples(Project project, @@ -60,14 +67,18 @@ public Result, ResponseCode> registerSamples(Project project, var sample = Sample.create(sampleCode, sampleRegistrationRequest); samplesToRegister.add(sample); }); - Result, ResponseCode> result = this.sampleRepository.addAll(project, - samplesToRegister); - if(result.isValue()) { + Collection registeredSamples; + try { + registeredSamples = this.sampleRepository.addAll(project, + samplesToRegister); + } catch (RuntimeException e) { + log.error(e.getMessage(), e); + return Result.fromError(ResponseCode.REGISTRATION_FAILED); + } + domainEventsCache.forEach( domainEvent -> DomainEventDispatcher.instance().dispatch(domainEvent)); - } - result.onError(Result::fromError); - return result; + return Result.fromValue(registeredSamples); } public void updateSamples(Project project, Collection updatedSamples) { @@ -101,6 +112,9 @@ public void updateSamples(Project project, Collection updat public void deleteSamples(Project project, BatchId batchId, Collection samples) { Objects.requireNonNull(samples); + samples.forEach( + sampleId -> confoundingVariableLevelRepository.deleteAllForSample(project.getId().value(), + sampleId.value())); sampleRepository.deleteAll(project, samples); samples.forEach(sampleId -> dispatchSuccessfulSampleDeletion(sampleId, batchId)); if(!samples.isEmpty()) { diff --git a/project-management/src/test/groovy/life/qbic/projectmanagement/domain/service/SampleDomainServiceSpec.groovy b/project-management/src/test/groovy/life/qbic/projectmanagement/domain/service/SampleDomainServiceSpec.groovy index f6e0de269..caff1e308 100644 --- a/project-management/src/test/groovy/life/qbic/projectmanagement/domain/service/SampleDomainServiceSpec.groovy +++ b/project-management/src/test/groovy/life/qbic/projectmanagement/domain/service/SampleDomainServiceSpec.groovy @@ -1,6 +1,6 @@ package life.qbic.projectmanagement.domain.service -import life.qbic.application.commons.Result + import life.qbic.domain.concepts.DomainEvent import life.qbic.domain.concepts.DomainEventDispatcher import life.qbic.domain.concepts.DomainEventSubscriber @@ -33,8 +33,8 @@ class SampleDomainServiceSpec extends Specification { and: SampleRepository testRepo = Mock(SampleRepository) - testRepo.addAll(_ as Project, _ as Collection) >> Result.fromValue(Arrays.asList(testSample)) - SampleDomainService sampleDomainService = new SampleDomainService(testRepo) + testRepo.addAll(_ as Project, _ as Collection) >> List.of(testSample) + SampleDomainService sampleDomainService = new SampleDomainService(testRepo, null) and: DomainEventSubscriber sampleRegistered = new DomainEventSubscriber() { @@ -57,8 +57,8 @@ class SampleDomainServiceSpec extends Specification { DomainEventDispatcher.instance().subscribe(sampleRegistered) when: - sampleDomainService.registerSamples(project, sampleCodesToRegistrationRequests) - + var a = sampleDomainService.registerSamples(project, sampleCodesToRegistrationRequests) + println(a) then: sampleRegistered.batchIdOfEvent.equals(sampleRegistrationRequest.assignedBatch()) diff --git a/sql/complete-schema.sql b/sql/complete-schema.sql new file mode 100644 index 000000000..bef9e5e1b --- /dev/null +++ b/sql/complete-schema.sql @@ -0,0 +1,642 @@ +CREATE TABLE IF NOT EXISTS `acl_class` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `class` varchar(100) NOT NULL, + `class_id_type` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_acl_class` (`class`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `acl_sid` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `principal` tinyint(1) NOT NULL, + `sid` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_acl_sid` (`sid`, `principal`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `acl_object_identity` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `object_id_class` bigint(20) unsigned NOT NULL, + `object_id_identity` varchar(36) NOT NULL, + `parent_object` bigint(20) unsigned DEFAULT NULL, + `owner_sid` bigint(20) unsigned DEFAULT NULL, + `entries_inheriting` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_acl_object_identity` (`object_id_class`, `object_id_identity`), + KEY `fk_acl_object_identity_parent` (`parent_object`), + KEY `fk_acl_object_identity_owner` (`owner_sid`), + CONSTRAINT `fk_acl_object_identity_class` FOREIGN KEY (`object_id_class`) REFERENCES `acl_class` (`id`), + CONSTRAINT `fk_acl_object_identity_owner` FOREIGN KEY (`owner_sid`) REFERENCES `acl_sid` (`id`), + CONSTRAINT `fk_acl_object_identity_parent` FOREIGN KEY (`parent_object`) REFERENCES `acl_object_identity` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `acl_entry` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `acl_object_identity` bigint(20) unsigned NOT NULL, + `ace_order` int(11) NOT NULL, + `sid` bigint(20) unsigned NOT NULL, + `mask` int(10) unsigned NOT NULL, + `granting` tinyint(1) NOT NULL, + `audit_success` tinyint(1) NOT NULL DEFAULT 1, + `audit_failure` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_acl_entry` (`acl_object_identity`, `ace_order`), + KEY `fk_acl_entry_acl` (`sid`), + CONSTRAINT `fk_acl_entry_acl` FOREIGN KEY (`sid`) REFERENCES `acl_sid` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_acl_entry_object` FOREIGN KEY (`acl_object_identity`) REFERENCES `acl_object_identity` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `projects_datamanager` +( + `projectId` varchar(255) NOT NULL, + `grantLabel` varchar(255) DEFAULT NULL, + `grantId` varchar(255) DEFAULT NULL, + `lastModified` datetime(6) NOT NULL, + `principalInvestigatorEmailAddress` varchar(255) DEFAULT NULL, + `principalInvestigatorFullName` varchar(255) DEFAULT NULL, + `projectCode` varchar(255) DEFAULT NULL, + `objective` varchar(2000) DEFAULT NULL, + `projectTitle` varchar(255) DEFAULT NULL, + `projectManagerEmailAddress` varchar(255) DEFAULT NULL, + `projectManagerFullName` varchar(255) DEFAULT NULL, + `responsibePersonEmailAddress` varchar(255) DEFAULT NULL, + `responsibePersonFullName` varchar(255) DEFAULT NULL, + PRIMARY KEY (`projectId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experiments_datamanager` +( + `id` varchar(255) NOT NULL, + `analyteIconName` varchar(31) NOT NULL DEFAULT 'default', + `experimentName` varchar(255) DEFAULT NULL, + `speciesIconName` varchar(31) NOT NULL DEFAULT 'default', + `specimenIconName` varchar(31) NOT NULL DEFAULT 'default', + `project` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FKgfrw5hlq3iy6ntf32wy0e8hr` (`project`), + CONSTRAINT `FKgfrw5hlq3iy6ntf32wy0e8hr` FOREIGN KEY (`project`) REFERENCES `projects_datamanager` (`projectId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experimental_group` +( + `experimentalGroupId` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `sampleSize` int(11) NOT NULL, + `experimentId` varchar(255) DEFAULT NULL, + PRIMARY KEY (`experimentalGroupId`), + KEY `FK25vvdiupupuwmehr3o97dh7fg` (`experimentId`), + CONSTRAINT `FK25vvdiupupuwmehr3o97dh7fg` FOREIGN KEY (`experimentId`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experimental_group_variableLevels` +( + `experimental_group_experimentalGroupId` bigint(20) NOT NULL, + `unit` varchar(255) DEFAULT NULL, + `value` varchar(255) DEFAULT NULL, + `variableName` varchar(255) DEFAULT NULL, + KEY `FKm81bdrf3y2gcsnmhuhjjj3y9j` (`experimental_group_experimentalGroupId`), + CONSTRAINT `FKm81bdrf3y2gcsnmhuhjjj3y9j` FOREIGN KEY (`experimental_group_experimentalGroupId`) REFERENCES `experimental_group` (`experimentalGroupId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experimental_variables` +( + `variableId` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `experimentId` varchar(255) DEFAULT NULL, + PRIMARY KEY (`variableId`), + KEY `FK4flido9vdvf9uff0g0rngrvpw` (`experimentId`), + CONSTRAINT `FK4flido9vdvf9uff0g0rngrvpw` FOREIGN KEY (`experimentId`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experimental_variables_levels` +( + `experimental_variables_variableId` bigint(20) NOT NULL, + `unit` varchar(255) DEFAULT NULL, + `value` varchar(255) DEFAULT NULL, + KEY `FKfljdny9mdnh19bbm3ii3j4bic` (`experimental_variables_variableId`), + CONSTRAINT `FKfljdny9mdnh19bbm3ii3j4bic` FOREIGN KEY (`experimental_variables_variableId`) REFERENCES `experimental_variables` (`variableId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experiments_datamanager_analytes` +( + `experiments_datamanager_id` varchar(255) NOT NULL, + `analytes` longtext DEFAULT NULL CHECK (json_valid(`analytes`)), + KEY `FKenl95t4n6dn8c90mcc9bdi7d3` (`experiments_datamanager_id`), + CONSTRAINT `FKenl95t4n6dn8c90mcc9bdi7d3` FOREIGN KEY (`experiments_datamanager_id`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experiments_datamanager_species` +( + `experiments_datamanager_id` varchar(255) NOT NULL, + `species` longtext DEFAULT NULL CHECK (json_valid(`species`)), + KEY `FK5jif824gfi7ho2dmk4lbp2cri` (`experiments_datamanager_id`), + CONSTRAINT `FK5jif824gfi7ho2dmk4lbp2cri` FOREIGN KEY (`experiments_datamanager_id`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experiments_datamanager_specimens` +( + `experiments_datamanager_id` varchar(255) NOT NULL, + `specimens` longtext DEFAULT NULL CHECK (json_valid(`specimens`)), + KEY `FKiebw7ho69dfx9osttd8sin73l` (`experiments_datamanager_id`), + CONSTRAINT `FKiebw7ho69dfx9osttd8sin73l` FOREIGN KEY (`experiments_datamanager_id`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `jobrunr_backgroundjobservers` +( + `id` char(36) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `workerPoolSize` int(11) NOT NULL, + `pollIntervalInSeconds` int(11) NOT NULL, + `firstHeartbeat` datetime(6) NOT NULL, + `lastHeartbeat` datetime(6) NOT NULL, + `running` int(11) NOT NULL, + `systemTotalMemory` bigint(20) NOT NULL, + `systemFreeMemory` bigint(20) NOT NULL, + `systemCpuLoad` decimal(3, 2) NOT NULL, + `processMaxMemory` bigint(20) NOT NULL, + `processFreeMemory` bigint(20) NOT NULL, + `processAllocatedMemory` bigint(20) NOT NULL, + `processCpuLoad` decimal(3, 2) NOT NULL, + `deleteSucceededJobsAfter` varchar(32) DEFAULT NULL, + `permanentlyDeleteJobsAfter` varchar(32) DEFAULT NULL, + `name` varchar(128) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `jobrunr_bgjobsrvrs_fsthb_idx` (`firstHeartbeat`), + KEY `jobrunr_bgjobsrvrs_lsthb_idx` (`lastHeartbeat`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `jobrunr_jobs` +( + `id` char(36) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `version` int(11) NOT NULL, + `jobAsJson` mediumtext DEFAULT NULL, + `jobSignature` varchar(512) NOT NULL, + `state` varchar(36) NOT NULL, + `createdAt` datetime(6) NOT NULL, + `updatedAt` datetime(6) NOT NULL, + `scheduledAt` datetime(6) DEFAULT NULL, + `recurringJobId` varchar(128) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `jobrunr_state_idx` (`state`), + KEY `jobrunr_job_signature_idx` (`jobSignature`), + KEY `jobrunr_job_created_at_idx` (`createdAt`), + KEY `jobrunr_job_scheduled_at_idx` (`scheduledAt`), + KEY `jobrunr_job_rci_idx` (`recurringJobId`), + KEY `jobrunr_jobs_state_updated_idx` (`state`, `updatedAt`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `jobrunr_metadata` +( + `id` varchar(156) NOT NULL, + `name` varchar(92) NOT NULL, + `owner` varchar(64) NOT NULL, + `value` text NOT NULL, + `createdAt` datetime(6) NOT NULL, + `updatedAt` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `jobrunr_migrations` +( + `id` char(36) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `script` varchar(64) NOT NULL, + `installedOn` varchar(29) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `jobrunr_recurring_jobs` +( + `id` char(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `version` int(11) NOT NULL, + `jobAsJson` text NOT NULL, + `createdAt` bigint(20) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `jobrunr_recurring_job_created_at_idx` (`createdAt`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ngs_measurements` +( + `measurement_id` varchar(255) NOT NULL, + `facility` varchar(255) DEFAULT NULL, + `flowcell` varchar(255) DEFAULT NULL, + `instrument` longtext DEFAULT NULL CHECK (json_valid(`instrument`)), + `libraryKit` varchar(255) DEFAULT NULL, + `measurementCode` varchar(255) DEFAULT NULL, + `IRI` varchar(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `projectId` varchar(255) DEFAULT NULL, + `registrationTime` datetime(6) DEFAULT NULL, + `samplePool` varchar(255) DEFAULT NULL, + `readType` varchar(255) DEFAULT NULL, + `runProtocol` varchar(255) DEFAULT NULL, + PRIMARY KEY (`measurement_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ontology_classes` +( + `id` bigint(20) NOT NULL, + `classIri` varchar(255) DEFAULT NULL, + `label` varchar(1024) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `description` varchar(2000) DEFAULT NULL, + `ontology` varchar(255) DEFAULT NULL, + `ontologyIri` varchar(255) DEFAULT NULL, + `ontologyVersion` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + FULLTEXT KEY `idx_fulltext_name` (`name`), + FULLTEXT KEY `idx_fulltext_label` (`label`) +) ENGINE = MyISAM + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ontology_classes_SEQ` +( + `next_val` bigint(20) DEFAULT NULL +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `permissions` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `personal_access_tokens` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `creationDate` datetime(6) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `duration` decimal(21, 0) DEFAULT NULL, + `tokenId` varchar(255) DEFAULT NULL, + `tokenValueEncrypted` varchar(255) DEFAULT NULL, + `userId` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_n2uptwhyydh9ff51ak8wkpi6g` (`tokenValueEncrypted`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `projects_offers` +( + `projectIdentifier` varchar(255) NOT NULL, + `offerIdentifier` varchar(255) DEFAULT NULL, + KEY `FKk2sfclbecq7f9htqutkw808xs` (`projectIdentifier`), + CONSTRAINT `FKk2sfclbecq7f9htqutkw808xs` FOREIGN KEY (`projectIdentifier`) REFERENCES `projects_datamanager` (`projectId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `proteomics_measurement` +( + `measurement_id` varchar(255) NOT NULL, + `digestionEnzyme` varchar(255) DEFAULT NULL, + `digestionMethod` varchar(255) DEFAULT NULL, + `enrichmentMethod` varchar(255) DEFAULT NULL, + `facility` varchar(255) DEFAULT NULL, + `injectionVolume` int(11) DEFAULT NULL, + `instrument` longtext DEFAULT NULL CHECK (json_valid(`instrument`)), + `labelType` varchar(255) DEFAULT NULL, + `lcColumn` varchar(255) DEFAULT NULL, + `lcmsMethod` varchar(255) DEFAULT NULL, + `measurementCode` varchar(255) DEFAULT NULL, + `IRI` varchar(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `projectId` varchar(255) DEFAULT NULL, + `registration` datetime(6) DEFAULT NULL, + `samplePool` varchar(255) DEFAULT NULL, + `technicalReplicateName` varchar(255) DEFAULT NULL, + PRIMARY KEY (`measurement_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `purchase_offer` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `file_content` longblob DEFAULT NULL, + `fileName` varchar(255) DEFAULT NULL, + `signed` bit(1) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `quality_control_upload` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `experiment_id` varchar(255) DEFAULT NULL, + `file_content` longblob DEFAULT NULL, + `fileName` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `quality_control` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `projectId` varchar(255) DEFAULT NULL, + `providedOn` datetime(6) DEFAULT NULL, + `qualityControlReference` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_ltx4csqq0sndl0vm9tnly0tem` (`qualityControlReference`), + CONSTRAINT `FKmxonhr463uotnq2u62dt0gga1` FOREIGN KEY (`qualityControlReference`) REFERENCES `quality_control_upload` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `roles` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `role_permission` +( + `userRoleId` bigint(20) NOT NULL, + `permissionId` bigint(20) NOT NULL, + KEY `FKqrvf0677p2qt9ymasidbqi8sf` (`permissionId`), + KEY `FKe3r4gqu0shl2iox9v427uvwdw` (`userRoleId`), + CONSTRAINT `FKe3r4gqu0shl2iox9v427uvwdw` FOREIGN KEY (`userRoleId`) REFERENCES `roles` (`id`), + CONSTRAINT `FKqrvf0677p2qt9ymasidbqi8sf` FOREIGN KEY (`permissionId`) REFERENCES `permissions` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `sample` +( + `sample_id` varchar(255) NOT NULL, + `analysis_method` varchar(255) DEFAULT NULL, + `assigned_batch_id` varchar(255) DEFAULT NULL, + `comment` varchar(255) DEFAULT NULL, + `experiment_id` varchar(255) DEFAULT NULL, + `experimentalGroupId` bigint(20) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `organism_id` varchar(255) DEFAULT NULL, + `code` varchar(255) DEFAULT NULL, + `analyte` text DEFAULT NULL, + `species` text DEFAULT NULL, + `specimen` text DEFAULT NULL, + PRIMARY KEY (`sample_id`), + KEY `FK5glb6os4rlub6rb133xi1xj0y` (`experimentalGroupId`), + CONSTRAINT `FK5glb6os4rlub6rb133xi1xj0y` FOREIGN KEY (`experimentalGroupId`) REFERENCES `experimental_group` (`experimentalGroupId`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `sample_batches` +( + `id` varchar(255) NOT NULL, + `createdOn` datetime(6) DEFAULT NULL, + `batchLabel` varchar(255) DEFAULT NULL, + `lastModified` datetime(6) DEFAULT NULL, + `isPilot` bit(1) DEFAULT NULL, + `version` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `sample_batches_sampleid` +( + `batch_id` varchar(255) NOT NULL, + `sample_id` varchar(255) DEFAULT NULL, + KEY `FKqkox27nsufoigg0vkdpqryga` (`batch_id`), + CONSTRAINT `FKqkox27nsufoigg0vkdpqryga` FOREIGN KEY (`batch_id`) REFERENCES `sample_batches` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `sample_statistics_entry` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `projectCode` varchar(255) DEFAULT NULL, + `projectId` varchar(255) DEFAULT NULL, + `sampleCounter` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `service_purchase` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `projectId` varchar(255) DEFAULT NULL, + `purchasedOn` datetime(6) DEFAULT NULL, + `offerReference` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK_8cs2g0gnxrgknqy4dhri6ltil` (`offerReference`), + CONSTRAINT `FKhi0nqiffluv4ov42lkt6u03ac` FOREIGN KEY (`offerReference`) REFERENCES `purchase_offer` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `specific_measurement_metadata_ngs` +( + `measurement_id` varchar(255) NOT NULL, + `comment` varchar(255) DEFAULT NULL, + `indexI5` varchar(255) DEFAULT NULL, + `indexI7` varchar(255) DEFAULT NULL, + `sample_id` varchar(255) DEFAULT NULL, + KEY `FK936j925a6pi06ojgafesihm5b` (`measurement_id`), + CONSTRAINT `FK936j925a6pi06ojgafesihm5b` FOREIGN KEY (`measurement_id`) REFERENCES `ngs_measurements` (`measurement_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `specific_measurement_metadata_pxp` +( + `measurement_id` varchar(255) NOT NULL, + `comment` varchar(255) DEFAULT NULL, + `fractionName` varchar(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `sample_id` varchar(255) DEFAULT NULL, + KEY `FKn0pfbmn6xtywvflgsf5q1hbrh` (`measurement_id`), + CONSTRAINT `FKn0pfbmn6xtywvflgsf5q1hbrh` FOREIGN KEY (`measurement_id`) REFERENCES `proteomics_measurement` (`measurement_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `user_role` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `userId` varchar(255) DEFAULT NULL, + `roleId` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + KEY `FKgjtm8gpb7flxkryafvaanmykq` (`roleId`), + CONSTRAINT `FKgjtm8gpb7flxkryafvaanmykq` FOREIGN KEY (`roleId`) REFERENCES `roles` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `users` +( + `id` varchar(255) NOT NULL, + `active` bit(1) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `encryptedPassword` varchar(255) DEFAULT NULL, + `fullName` varchar(255) DEFAULT NULL, + `registrationDate` datetime(6) DEFAULT NULL, + `userName` varchar(255) DEFAULT NULL, + `oidcId` varchar(255) DEFAULT NULL, + `oidcIssuer` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `experiments_datamanager_confounding_variables` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `experimentId` VARCHAR(255) NOT NULL, + `name` VARCHAR(255) NOT NULL, + KEY `FK_experiment_id` (`experimentId`), + CONSTRAINT `FK_experiment_id` FOREIGN KEY (`experimentId`) REFERENCES `experiments_datamanager` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `confounding_variable_levels` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `sampleId` VARCHAR(255) NOT NULL, + `variableId` bigint(20) NOT NULL, + `value` VARCHAR(255) NOT NULL, + KEY `FK_sample_id` (`sampleId`), + CONSTRAINT `FK_sample_id` FOREIGN KEY (`sampleId`) REFERENCES `sample` (`sample_id`), + KEY `FK_variable_id` (`variableId`), + CONSTRAINT `FK_variable_id` FOREIGN KEY (`variableId`) REFERENCES `experiments_datamanager_confounding_variables` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 + COLLATE utf8mb4_unicode_ci; + + + +DROP VIEW IF EXISTS data_management.project_measurements; + +CREATE view data_management.project_measurements as +SELECT `projects`.`projectId` AS `projectId`, + COALESCE(`proteomics`.`amountPxpMeasurements`, 0) AS `amountPxpMeasurements`, /*do not allow null values*/ + COALESCE(`ngs`.`amountNgsMeasurements`, 0) AS `amountNgsMeasurements` /*do not allow null values*/ +FROM (projects_datamanager projects LEFT JOIN (SELECT ngs.`projectId` AS `projectId`, + count(ngs.`measurementCode`) AS `amountNgsMeasurements` + FROM ngs_measurements ngs + GROUP BY ngs.`projectId`) AS `ngs` + ON (`projects`.`projectId` = `ngs`.`projectId`)) + LEFT JOIN (SELECT `projects`.`projectId` AS `projectId`, + `pxp`.`amountPxpMeasurements` AS `amountPxpMeasurements` + from (`data_management`.`projects_datamanager` `projects` left join (select `p`.`projectId` AS `pID`, + count(`p`.`measurementCode`) AS `amountPxpMeasurements` + from `data_management`.`proteomics_measurement` `p` + group by `p`.`projectId`) `pxp` + on (`projects`.`projectId` = `pxp`.`pID`))) `proteomics` + on (`projects`.`projectId` = `proteomics`.`projectId`); + +DROP VIEW IF EXISTS data_management.project_userinfo; + +CREATE VIEW data_management.project_userinfo as +SELECT `o_identity`.`object_id_identity` AS `projectId`, + `u`.`userName` AS `userName`, + `u`.`id` AS `userId` +FROM (((`data_management`.`acl_entry` LEFT JOIN (SELECT `sid`.`id` AS `id`, + `sid`.`principal` AS `principal`, + `sid`.`sid` AS `sid` + FROM `data_management`.`acl_sid` `sid` + WHERE `sid`.`principal` = 1) `sid` + on (`data_management`.`acl_entry`.`sid` = `sid`.`id`)) LEFT JOIN `data_management`.`users` `u` + on (`sid`.`sid` = `u`.`id`)) LEFT JOIN `data_management`.`acl_object_identity` `o_identity` + on (`data_management`.`acl_entry`.`acl_object_identity` = `o_identity`.`id`)) +WHERE `sid`.`sid` IS NOT NULL; + +DROP VIEW IF EXISTS data_management.project_overview; + +CREATE VIEW project_overview AS +SELECT `pd`.`projectId` AS `projectId`, + `pd`.`projectCode` AS `projectCode`, + `pd`.`projectTitle` AS `projectTitle`, + `pd`.`lastModified` AS `lastModified`, + `pd`.`principalInvestigatorFullName` AS `principalInvestigatorFullName`, + `pd`.`projectManagerFullName` AS `projectManagerFullName`, + `pd`.`responsibePersonFullName` AS `responsibePersonFullName`, + `m`.`amountNgsMeasurements` AS `amountNgsMeasurements`, + `m`.`amountPxpMeasurements` AS `amountPxpMeasurements`, + `users`.`usernames` AS `usernames`, + `users`.`userInfos` AS `userInfos` +FROM projects_datamanager pd + LEFT JOIN project_measurements m ON pd.projectId = m.projectId + LEFT JOIN (SELECT project_userinfo.projectId, + GROUP_CONCAT(project_userinfo.userName SEPARATOR ', ') AS `usernames`, + JSON_ARRAYAGG( + JSON_OBJECT( + 'userId', project_userinfo.userId, + 'userName', project_userinfo.userName + )) AS `userInfos` + FROM project_userinfo + GROUP BY projectId) AS users ON users.projectId = pd.projectId; diff --git a/sql/setup-roles.sql b/sql/insert-default-values.sql similarity index 89% rename from sql/setup-roles.sql rename to sql/insert-default-values.sql index 14566183d..5b849770f 100644 --- a/sql/setup-roles.sql +++ b/sql/insert-default-values.sql @@ -22,3 +22,6 @@ VALUES (1, 1), # admin can change ownership on entities (3, 3), # project manager can create, modify and remove access control entries (3, 4) # project manager can create projects ; + +INSERT INTO acl_class(id, class, class_id_type) +VALUES (1, 'life.qbic.projectmanagement.domain.model.project.Project', 'java.lang.String'); diff --git a/sql/modify-ontology-table.sql b/sql/modify-ontology-table.sql deleted file mode 100644 index 7c572e216..000000000 --- a/sql/modify-ontology-table.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE ontology_classes - ENGINE = MyISAM; -ALTER TABLE ontology_classes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci -ALTER TABLE ontology_classes - ADD FULLTEXT `idx_fulltext_name` (name); -ALTER TABLE ontology_classes - ADD FULLTEXT `idx_fulltext_label` (label); diff --git a/sql/project_measurements_view.sql b/sql/project_measurements_view.sql deleted file mode 100644 index 28bd5d13b..000000000 --- a/sql/project_measurements_view.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE VIEW project_measurements AS -SELECT projects.projectId, amountPxpMeasurements, ngs.amountNgsMeasurements -FROM projects_datamanager projects - LEFT JOIN (SELECT projectId, COUNT(n.measurementCode) as amountNgsMeasurements - FROM ngs_measurements n - group by projectId) as ngs ON projects.projectId = ngs.projectId - LEFT JOIN (SELECT projects.projectId, amountPxpMeasurements - FROM projects_datamanager projects - LEFT JOIN (SELECT projectId as pID, - COUNT(p.measurementCode) as amountPxpMeasurements - FROM proteomics_measurement p - group by pID) as pxp - ON projects.projectId = pxp.pID) as proteomics - ON projects.projectId = proteomics.projectId; diff --git a/sql/setup-acl.sql b/sql/setup-acl.sql deleted file mode 100644 index 06db7bdef..000000000 --- a/sql/setup-acl.sql +++ /dev/null @@ -1,47 +0,0 @@ -CREATE TABLE acl_sid -( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - principal BOOLEAN NOT NULL, - sid VARCHAR(100) NOT NULL, - UNIQUE KEY unique_acl_sid (sid, principal) -) ENGINE = InnoDB; - -CREATE TABLE acl_class -( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - class VARCHAR(100) NOT NULL, - class_id_type VARCHAR(100) NOT NULL, - UNIQUE KEY uk_acl_class (class) -) ENGINE = InnoDB; - -CREATE TABLE acl_object_identity -( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - object_id_class BIGINT UNSIGNED NOT NULL, - object_id_identity VARCHAR(36) NOT NULL, - parent_object BIGINT UNSIGNED, - owner_sid BIGINT UNSIGNED, - entries_inheriting BOOLEAN NOT NULL, - UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity), - CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), - CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) -) ENGINE = InnoDB; - -CREATE TABLE acl_entry -( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - acl_object_identity BIGINT UNSIGNED NOT NULL, - ace_order INTEGER NOT NULL, - sid BIGINT UNSIGNED NOT NULL, - mask INTEGER UNSIGNED NOT NULL, - granting BOOLEAN NOT NULL, - audit_success BOOLEAN NOT NULL DEFAULT true, - audit_failure BOOLEAN NOT NULL, - UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order), - CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) ON DELETE CASCADE -) ENGINE = InnoDB; - -INSERT INTO acl_class(id, class, class_id_type) -VALUES (1, 'life.qbic.projectmanagement.domain.model.project.Project', 'java.lang.String'); diff --git a/sql/setup-views.sql b/sql/setup-views.sql deleted file mode 100644 index b8b9d1b7a..000000000 --- a/sql/setup-views.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE VIEW project_userinfo AS -SELECT o_identity.object_id_identity AS `projectId`, - u.userName AS `userName`, - u.id AS `userId` -FROM acl_entry - LEFT JOIN (SELECT * - FROM acl_sid AS sid - WHERE sid.principal = 1) AS sid ON acl_entry.sid = sid.id - LEFT JOIN users u ON sid.sid = u.id - LEFT JOIN acl_object_identity o_identity ON acl_entry.acl_object_identity = o_identity.id -WHERE sid.sid IS NOT NULL; - - -CREATE VIEW project_overview AS -SELECT pd.projectId, - projectCode, - projectTitle, - lastModified, - principalInvestigatorFullName, - projectManagerFullName, - responsibePersonFullName, - amountNgsMeasurements, - amountPxpMeasurements, - users.usernames, - users.userInfos -FROM projects_datamanager pd - LEFT JOIN project_measurements m ON pd.projectId = m.projectId - LEFT JOIN (SELECT projectId, - GROUP_CONCAT(userName SEPARATOR ', ') AS `usernames`, - JSON_ARRAYAGG( - JSON_OBJECT( - 'userId', userId, - 'userName', userName - )) AS `userInfos` - FROM project_userinfo - GROUP BY projectId) as users ON users.projectId = pd.projectId; diff --git a/user-interface/src/main/bundles/dev.bundle b/user-interface/src/main/bundles/dev.bundle index a198bd6c3..03c6d6d69 100644 Binary files a/user-interface/src/main/bundles/dev.bundle and b/user-interface/src/main/bundles/dev.bundle differ diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/MetadataConverter.java b/user-interface/src/main/java/life/qbic/datamanager/parser/MetadataConverter.java index 9b00aef0b..027158f2e 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/MetadataConverter.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/MetadataConverter.java @@ -8,7 +8,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -346,65 +345,57 @@ private List convertNewNGSMeasurement(ParsingResult parsing } private boolean looksLikeNgsMeasurement(Collection properties, boolean ignoreID) { - var formattedProperties = properties.stream().map(String::toLowerCase) - .collect(Collectors.toList()); - Map hitMap; + var sanitizedColumnHeaders = properties.stream() + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); + Set requiredColumnHeaders; if (ignoreID) { - hitMap = countHits(formattedProperties, - Arrays.stream(NGSMeasurementRegisterColumn.values()) - .map(NGSMeasurementRegisterColumn::headerName) - .map(Sanitizer::headerEncoder) - .collect(Collectors.toSet()), NGSMeasurementEditColumn.MEASUREMENT_ID.headerName()); + requiredColumnHeaders = Arrays.stream(NGSMeasurementRegisterColumn.values()) + .map(NGSMeasurementRegisterColumn::headerName) + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); } else { - hitMap = countHits(formattedProperties, - Arrays.stream(NGSMeasurementEditColumn.values()) - .map(NGSMeasurementEditColumn::headerName) - .map(Sanitizer::headerEncoder) - .collect(Collectors.toSet())); + requiredColumnHeaders = Arrays.stream(NGSMeasurementEditColumn.values()) + .map(NGSMeasurementEditColumn::headerName) + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); } - var missingProperties = new ArrayList<>(); - for (Entry entry : hitMap.entrySet()) { - if (entry.getValue() == 0) { - missingProperties.add(entry.getKey()); - } - } - if (missingProperties.isEmpty()) { - return true; - } else { - log.debug("Missing properties for NGS measurement: %s".formatted(missingProperties)); - } - return false; + return hasAllRequiredProperties(requiredColumnHeaders, sanitizedColumnHeaders, + "Missing properties for NGS measurement: %s"); } private boolean looksLikeProteomicsMeasurement(Collection properties, boolean ignoreID) { - var formattedProperties = properties.stream().map(String::toLowerCase) - .collect(Collectors.toList()); - Map hitMap; + var sanitizedColumnHeaders = properties.stream() + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); + Set requiredColumnHeaders; if (ignoreID) { - hitMap = countHits(formattedProperties, - Arrays.stream(ProteomicsMeasurementRegisterColumn.values()) - .map(ProteomicsMeasurementRegisterColumn::headerName) - .map(Sanitizer::headerEncoder) - .collect(Collectors.toSet()), - ProteomicsMeasurementEditColumn.MEASUREMENT_ID.headerName()); + requiredColumnHeaders = Arrays.stream(ProteomicsMeasurementRegisterColumn.values()) + .map(ProteomicsMeasurementRegisterColumn::headerName) + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); } else { - hitMap = countHits(formattedProperties, - Arrays.stream(ProteomicsMeasurementEditColumn.values()) - .map(ProteomicsMeasurementEditColumn::headerName) - .map(Sanitizer::headerEncoder) - .collect(Collectors.toSet())); + requiredColumnHeaders = Arrays.stream(ProteomicsMeasurementEditColumn.values()) + .map(ProteomicsMeasurementEditColumn::headerName) + .map(Sanitizer::headerEncoder) + .collect(Collectors.toSet()); + } + return hasAllRequiredProperties(requiredColumnHeaders, sanitizedColumnHeaders, + "Missing properties for proteomics measurement: %s"); + } + + private static boolean hasAllRequiredProperties(Set requiredProperties, + Set presentProperties, String missingErrorMessage) { + if (presentProperties.containsAll(requiredProperties)) { + return true; } var missingProperties = new ArrayList<>(); - for (Entry entry : hitMap.entrySet()) { - if (entry.getValue() == 0) { - missingProperties.add(entry.getKey()); + for (String requiredProperty : requiredProperties) { + if (!presentProperties.contains(requiredProperty)) { + missingProperties.add(requiredProperty); } } - if (missingProperties.isEmpty()) { - return true; - } else { - log.debug("Missing properties for proteomics measurement: %s".formatted(missingProperties)); - } + log.debug(missingErrorMessage.formatted(missingProperties)); return false; } } diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/ParsingResult.java b/user-interface/src/main/java/life/qbic/datamanager/parser/ParsingResult.java index fe1e8992e..b6b53eed8 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/ParsingResult.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/ParsingResult.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; /** * Parsing Result @@ -71,7 +72,7 @@ public Row getRow(int rowIndex) { } public String getValueOrDefault(int rowIndex, String columnHeader, String defaultValue) { - var key = Sanitizer.headerEncoder(columnHeader); + var key = columnHeader; if (!columnMap().containsKey(key)) { return defaultValue; } @@ -79,6 +80,15 @@ public String getValueOrDefault(int rowIndex, String columnHeader, String defaul return row.values().get(columnMap().get(key)); } + public Optional getValue(int rowIndex, String columnHeader) { + var key = columnHeader; + if (!columnMap().containsKey(key)) { + return Optional.empty(); + } + Row row = getRow(rowIndex); + return Optional.ofNullable(row.values().get(columnMap().get(key))); + } + public record Row(List values) { public Row(List values) { this.values = List.copyOf(values); diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/Sanitizer.java b/user-interface/src/main/java/life/qbic/datamanager/parser/Sanitizer.java index f518a7c41..4dd250b47 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/Sanitizer.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/Sanitizer.java @@ -24,7 +24,7 @@ public class Sanitizer { */ public static String headerEncoder(String value) { Objects.requireNonNull(value); - return value.replaceAll(ASTERIX, "").trim().toLowerCase(); + return value.replaceAll(ASTERIX, "").trim(); } /** diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/EditColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/EditColumn.java index ae3aebaef..07fd083f1 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/EditColumn.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/EditColumn.java @@ -2,6 +2,8 @@ import java.util.Arrays; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import life.qbic.datamanager.parser.Column; import life.qbic.datamanager.parser.ExampleProvider; import life.qbic.datamanager.parser.ExampleProvider.Helper; @@ -80,6 +82,10 @@ public static int maxColumnIndex() { this.mandatory = mandatory; } + public static Set headerNames() { + return Arrays.stream(values()).map(EditColumn::headerName).collect(Collectors.toSet()); + } + public String headerName() { return headerName; } diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/RegisterColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/RegisterColumn.java index 524b07b8e..453f903c2 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/RegisterColumn.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/RegisterColumn.java @@ -2,6 +2,8 @@ import java.util.Arrays; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import life.qbic.datamanager.parser.Column; import life.qbic.datamanager.parser.ExampleProvider; import life.qbic.datamanager.parser.ExampleProvider.Helper; @@ -66,6 +68,10 @@ public static int maxColumnIndex() { .max().orElse(0); } + public static Set headerNames() { + return Arrays.stream(values()).map(RegisterColumn::headerName).collect(Collectors.toSet()); + } + /** * @param headerName the name in the header * @param columnIndex the index of the column this property is in diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/SampleInformationExtractor.java b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/SampleInformationExtractor.java index 104230d7c..27b0f189b 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/parser/sample/SampleInformationExtractor.java +++ b/user-interface/src/main/java/life/qbic/datamanager/parser/sample/SampleInformationExtractor.java @@ -1,8 +1,12 @@ package life.qbic.datamanager.parser.sample; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import life.qbic.datamanager.parser.ParsingResult; +import life.qbic.datamanager.parser.Sanitizer; /** * Extracts sample information from a parsing result. @@ -13,7 +17,6 @@ * @since 1.5.0 */ public class SampleInformationExtractor { - /** * Extract information for new samples from a parsing result. * @@ -24,6 +27,7 @@ public class SampleInformationExtractor { public List extractInformationForNewSamples( ParsingResult parsingResult) { var result = new ArrayList(); + for (int i = 0; i < parsingResult.rows().size(); i++) { var sampleName = parsingResult.getValueOrDefault(i, RegisterColumn.SAMPLE_NAME.headerName(), ""); @@ -36,6 +40,23 @@ public List extractInformationForNewSamples( var analyte = parsingResult.getValueOrDefault(i, RegisterColumn.ANALYTE.headerName(), ""); var specimen = parsingResult.getValueOrDefault(i, RegisterColumn.SPECIMEN.headerName(), ""); var comment = parsingResult.getValueOrDefault(i, RegisterColumn.COMMENT.headerName(), ""); + + var sanitizedHeaderNames = RegisterColumn.headerNames().stream() + .map(Sanitizer::headerEncoder) + .collect( + Collectors.toSet()); + + var confoundingVariableColumns = new HashMap(); + parsingResult.columnMap().forEach((key, value) -> { + if (!sanitizedHeaderNames.contains(key)) { + confoundingVariableColumns.put(key, value); + } + }); + var confoundingVariables = new HashMap(); + final int finalI = i; + confoundingVariableColumns.forEach((key, value) -> parsingResult.getValue(finalI, key) + .ifPresent(it -> confoundingVariables.put(key, it))); + result.add(new SampleInformationForNewSample( sampleName, analysisMethod, @@ -44,7 +65,9 @@ public List extractInformationForNewSamples( species, analyte, specimen, - comment)); + comment, + confoundingVariables + )); } return result; } @@ -71,6 +94,23 @@ public List extractInformationForExistingSam var analyte = parsingResult.getValueOrDefault(i, EditColumn.ANALYTE.headerName(), ""); var specimen = parsingResult.getValueOrDefault(i, EditColumn.SPECIMEN.headerName(), ""); var comment = parsingResult.getValueOrDefault(i, EditColumn.COMMENT.headerName(), ""); + + var sanitizedHeaderNames = EditColumn.headerNames().stream() + .map(Sanitizer::headerEncoder) + .collect( + Collectors.toSet()); + + var confoundingVariableColumns = new HashMap(); + parsingResult.columnMap().forEach((key, value) -> { + if (!sanitizedHeaderNames.contains(key)) { + confoundingVariableColumns.put(key, value); + } + }); + var confoundingVariables = new HashMap(); + final int finalI = i; + confoundingVariableColumns.forEach((key, value) -> parsingResult.getValue(finalI, key) + .ifPresent(it -> confoundingVariables.put(key, it))); + result.add(new SampleInformationForExistingSample(sampleCode, sampleName, analysisMethod, @@ -79,7 +119,8 @@ public List extractInformationForExistingSam species, specimen, analyte, - comment + comment, + confoundingVariables )); } return result; @@ -102,7 +143,8 @@ public record SampleInformationForNewSample( String species, String analyte, String specimen, - String comment + String comment, + Map confoundingVariables ) { } @@ -126,7 +168,8 @@ public record SampleInformationForExistingSample( String species, String specimen, String analyte, - String comment) { + String comment, + Map confoundingVariables) { } } diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateService.java b/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateService.java index 2b3f5ac7d..7fbbedc87 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateService.java +++ b/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateService.java @@ -5,6 +5,10 @@ import java.util.Objects; import life.qbic.datamanager.templates.sample.SampleBatchRegistrationTemplate; import life.qbic.datamanager.templates.sample.SampleBatchUpdateTemplate; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableLevel; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference; import life.qbic.projectmanagement.application.experiment.ExperimentInformationService; import life.qbic.projectmanagement.application.sample.PropertyConversion; import life.qbic.projectmanagement.application.sample.SampleInformationService; @@ -33,12 +37,15 @@ public class TemplateService { private final ExperimentInformationService experimentInfoService; private final SampleInformationService sampleInformationService; + private final ConfoundingVariableService confoundingVariableService; @Autowired public TemplateService(ExperimentInformationService experimentInfoService, - SampleInformationService sampleInformationService) { + SampleInformationService sampleInformationService, + ConfoundingVariableService confoundingVariableService) { this.experimentInfoService = Objects.requireNonNull(experimentInfoService); this.sampleInformationService = Objects.requireNonNull(sampleInformationService); + this.confoundingVariableService = confoundingVariableService; } /** @@ -73,7 +80,11 @@ public XSSFWorkbook sampleBatchRegistrationXLSXTemplate(String projectId, String var experiment = experimentInfoService.find(projectId, ExperimentId.parse(experimentId)) .orElseThrow( NoSuchExperimentException::new); - return createWorkbookFromExperiment(experiment); + List confoundingVariables = confoundingVariableService.listConfoundingVariablesForExperiment( + projectId, new ExperimentReference(experimentId)).stream() + .map(ConfoundingVariableInformation::variableName) + .toList(); + return createWorkbookFromExperiment(experiment, confoundingVariables); } @PreAuthorize( @@ -86,17 +97,25 @@ public XSSFWorkbook sampleBatchUpdateXLSXTemplate(BatchId batchId, String projec NoSuchExperimentException::new); var samples = sampleInformationService.retrieveSamplesForExperiment( ExperimentId.parse(experimentId)); + List confoundingVariables = confoundingVariableService.listConfoundingVariablesForExperiment( + projectId, new ExperimentReference(experimentId)); + List confoundingVariableLevels = confoundingVariableService.listLevelsForVariables( + projectId, + confoundingVariables.stream().map(ConfoundingVariableInformation::id).toList()); samples.onError(responseCode -> { throw new SampleSearchException(); }); var samplesInBatch = samples.getValue().stream() .filter(sample -> sample.assignedBatch().equals(batchId)) .toList(); - return createPrefilledWorkbookFromExperiment(experiment, samplesInBatch); + return createPrefilledWorkbookFromExperiment(experiment, samplesInBatch, confoundingVariables, + confoundingVariableLevels); } private XSSFWorkbook createPrefilledWorkbookFromExperiment(Experiment experiment, - List samples) { + List samples, List confoundingVariables, + List confoundingVariableLevels) { + var conditions = experiment.getExperimentalGroups().stream().map(ExperimentalGroup::condition) .map( PropertyConversion::toString).toList(); @@ -106,13 +125,18 @@ private XSSFWorkbook createPrefilledWorkbookFromExperiment(Experiment experiment var analytes = experiment.getAnalytes().stream().map(PropertyConversion::toString).toList(); var analysisMethods = Arrays.stream(AnalysisMethod.values()).map(AnalysisMethod::abbreviation) .toList(); + return SampleBatchUpdateTemplate.createUpdateTemplate(samples, conditions, species, specimen, analytes, - analysisMethods, experimentalGroups); + analysisMethods, + experimentalGroups, + confoundingVariables, + confoundingVariableLevels); } - private XSSFWorkbook createWorkbookFromExperiment(Experiment experiment) { + private XSSFWorkbook createWorkbookFromExperiment(Experiment experiment, + List confoundingVariables) { var conditions = experiment.getExperimentalGroups().stream().map(ExperimentalGroup::condition) .map( PropertyConversion::toString).toList(); @@ -124,7 +148,8 @@ private XSSFWorkbook createWorkbookFromExperiment(Experiment experiment) { return SampleBatchRegistrationTemplate.createRegistrationTemplate( conditions, species, specimen, analytes, - analysisMethods); + analysisMethods, + confoundingVariables); } public static class NoSuchExperimentException extends RuntimeException { diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchRegistrationTemplate.java b/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchRegistrationTemplate.java index 68a1815c7..89ab37434 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchRegistrationTemplate.java +++ b/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchRegistrationTemplate.java @@ -69,7 +69,7 @@ public class SampleBatchRegistrationTemplate { */ public static XSSFWorkbook createRegistrationTemplate(List conditions, List species, List specimen, List analytes, - List analysisToPerform) { + List analysisToPerform, List confoundingVariables) { XSSFWorkbook workbook = new XSSFWorkbook(); var readOnlyHeaderStyle = createReadOnlyHeaderCellStyle(workbook); var boldCellStyle = createBoldCellStyle(workbook); @@ -102,6 +102,16 @@ public static XSSFWorkbook createRegistrationTemplate(List conditions, helper.description())); } + var columnOffset = RegisterColumn.maxColumnIndex(); + for (int confoundingVariableIndex = 0; confoundingVariableIndex < confoundingVariables.size(); + confoundingVariableIndex++) { + String variableName = confoundingVariables.get(confoundingVariableIndex); + int columnIndex = confoundingVariableIndex + columnOffset + 1; + var cell = XLSXTemplateHelper.getOrCreateCell(header, columnIndex); + cell.setCellValue(variableName); + cell.setCellStyle(boldCellStyle); + } + // add property information order of columns matters!! for (RegisterColumn column : Arrays.stream( RegisterColumn.values()) diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchUpdateTemplate.java b/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchUpdateTemplate.java index c865454e7..d71342091 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchUpdateTemplate.java +++ b/user-interface/src/main/java/life/qbic/datamanager/templates/sample/SampleBatchUpdateTemplate.java @@ -12,14 +12,20 @@ import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import life.qbic.datamanager.parser.ExampleProvider.Helper; import life.qbic.datamanager.parser.sample.EditColumn; import life.qbic.datamanager.templates.XLSXTemplateHelper; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableLevel; import life.qbic.projectmanagement.application.sample.PropertyConversion; import life.qbic.projectmanagement.domain.model.experiment.Condition; import life.qbic.projectmanagement.domain.model.experiment.ExperimentalGroup; import life.qbic.projectmanagement.domain.model.sample.Sample; +import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Row; @@ -36,27 +42,32 @@ public class SampleBatchUpdateTemplate { public static final int MAX_ROW_INDEX_TO = 2000; + private SampleBatchUpdateTemplate() { + } /** * Creates a {@link XSSFWorkbook} that contains a prefilled sheet of sample metadata based on the * provided list of {@link Sample}. * - * @param samples a list of samples with metadata that will be used to fill the - * spreadsheet - * @param conditions a list of conditions that are available for selection - * @param species a list of species that are available for selection - * @param specimen a list of specimen that are available for selection - * @param analytes a list of analytes that are available for selection - * @param analysisToPerform a list of analysis types available for the sample measurement - * @param experimentalGroups a list of experimental groups the samples belong to + * @param samples a list of samples with metadata that will be used to fill the + * spreadsheet + * @param conditions a list of conditions that are available for selection + * @param species a list of species that are available for selection + * @param specimen a list of specimen that are available for selection + * @param analytes a list of analytes that are available for selection + * @param analysisToPerform a list of analysis types available for the sample measurement + * @param experimentalGroups a list of experimental groups the samples belong to + * @param confoundingVariables a list of confounding variables in the experiment + * @param confoundingVariableLevels a list of existing levels for confounding variables * @return a workbook with a prefilled sheet at tab index 0 that contains the sample metadata * @since 1.5.0 */ public static XSSFWorkbook createUpdateTemplate(List samples, List conditions, List species, List specimen, List analytes, - List analysisToPerform, List experimentalGroups) { - + List analysisToPerform, List experimentalGroups, + List confoundingVariables, + List confoundingVariableLevels) { XSSFWorkbook workbook = new XSSFWorkbook(); var readOnlyCellStyle = createReadOnlyCellStyle(workbook); var readOnlyHeaderStyle = XLSXTemplateHelper.createReadOnlyHeaderCellStyle(workbook); @@ -88,6 +99,21 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List confoundingVariableColumnIndices = new HashMap<>(); + var columnOffset = EditColumn.maxColumnIndex(); + for (int confoundingVariableIndex = 0; confoundingVariableIndex < confoundingVariables.size(); + confoundingVariableIndex++) { + ConfoundingVariableInformation confoundingVariableInformation = confoundingVariables.get( + confoundingVariableIndex); + String variableName = confoundingVariableInformation.variableName(); + int columnIndex = confoundingVariableIndex + columnOffset + 1; + confoundingVariableColumnIndices.put(confoundingVariableInformation, columnIndex); + var cell = XLSXTemplateHelper.getOrCreateCell(header, + columnIndex); + cell.setCellValue(variableName); + cell.setCellStyle(boldCellStyle); + } + // add property information order of columns matters!! for (EditColumn column : Arrays.stream( EditColumn.values()) @@ -111,7 +137,7 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List group.id() == sample.experimentalGroupId()).findFirst().orElseThrow(); fillRowWithSampleMetadata(row, sample, experimentalGroup.condition(), defaultStyle, - readOnlyCellStyle); + readOnlyCellStyle, confoundingVariableLevels, confoundingVariableColumnIndices); rowIndex++; } @@ -167,7 +193,7 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List samples, List confoundingVariableLevels, + Map confoundingVariableColumnMap) { for (EditColumn column : EditColumn.values()) { var value = switch (column) { case SAMPLE_ID -> sample.sampleCode().code(); @@ -196,5 +224,16 @@ private static void fillRowWithSampleMetadata(Row row, Sample sample, cell.setCellStyle(readOnlyCellStyle); } } + confoundingVariableColumnMap.forEach((variableInformation, columnIndex) -> { + Optional levelOfSample = confoundingVariableLevels.stream() + .filter(level -> level.variable().equals(variableInformation.id())) + .filter(level -> level.sample().id().equals(sample.sampleId().value())) + .findAny(); + levelOfSample.ifPresent(level -> { + Cell cell = getOrCreateCell(row, columnIndex); + cell.setCellValue(level.level()); + cell.setCellStyle(defaultStyle); + }); + }); } } diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/ConfoundingView.java b/user-interface/src/main/java/life/qbic/datamanager/views/ConfoundingView.java new file mode 100644 index 000000000..53360def3 --- /dev/null +++ b/user-interface/src/main/java/life/qbic/datamanager/views/ConfoundingView.java @@ -0,0 +1,170 @@ +package life.qbic.datamanager.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.renderer.TextRenderer; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.Location; +import com.vaadin.flow.router.OptionalParameter; +import com.vaadin.flow.router.QueryParameters; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import com.vaadin.flow.spring.annotation.UIScope; +import jakarta.annotation.security.PermitAll; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableLevel; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.SampleReference; +import life.qbic.projectmanagement.application.sample.SampleInformationService; +import life.qbic.projectmanagement.domain.model.experiment.ExperimentId; +import life.qbic.projectmanagement.domain.model.sample.Sample; +import life.qbic.projectmanagement.domain.model.sample.SampleId; + +/** + * TODO! REMOVE!!! + * short description + * + *

detailed description

+ * + * @since + */ +@Route(value = "confounding", layout = UserMainLayout.class) +@PermitAll +@SpringComponent +@UIScope +public class ConfoundingView extends FormLayout implements HasUrlParameter { + + private String projectId; + private ExperimentId experimentId; + private final TextArea textArea; + private final TextField variableAdding; + private final TextField levelAddingLevelValue; + private final ComboBox levelAddingSample; + private final ComboBox variableSelect; + + private final ConfoundingVariableService confoundingVariableService; + private final SampleInformationService sampleInformationService; + + @Override + public void setParameter(BeforeEvent event, + @OptionalParameter String parameter) { + Location location = event.getLocation(); + QueryParameters queryParameters = location.getQueryParameters(); + this.projectId = queryParameters.getSingleParameter("project") + .orElseThrow(); + this.experimentId = queryParameters.getSingleParameter("experiment") + .map(ExperimentId::parse).orElseThrow(); + contextSet(); + } + + private void contextSet() { + ExperimentReference experimentReference = new ExperimentReference(experimentId.value()); + reloadVariables(experimentReference); + reloadSamples(experimentReference); + + } + + private void reloadSamples(ExperimentReference experimentReference) { + var samples = sampleInformationService.retrieveSamplesForExperiment( + ExperimentId.parse(experimentReference.id())).valueOrElse(List.of()); + levelAddingSample.setItems(samples); + } + + private void reloadVariables(ExperimentReference experimentReference) { + ConfoundingVariableInformation selectedValue = variableSelect.getValue(); + List confoundingVariables = confoundingVariableService.listConfoundingVariablesForExperiment( + projectId, + experimentReference); + variableSelect.setItems(confoundingVariables); + variableSelect.setValue(selectedValue); + } + + public ConfoundingView(ConfoundingVariableService confoundingVariableService, + SampleInformationService sampleInformationService) { + this.confoundingVariableService = confoundingVariableService; + this.sampleInformationService = sampleInformationService; + levelAddingLevelValue = new TextField(); + levelAddingSample = new ComboBox<>(); + Button addLevelButton = new Button("Add Level"); + textArea = new TextArea(); + textArea.setReadOnly(true); + textArea.setWidthFull(); + variableAdding = new TextField(); + Button addVariableButton = new Button("Add Variable"); + variableSelect = new ComboBox<>(); + variableSelect.setItemLabelGenerator(item -> item.variableName() + "[" + item.id().id() + "]"); + variableSelect.setRenderer( + new TextRenderer<>(item -> item.variableName() + "[" + item.id().id() + "]")); + addFormItem(new Div(variableAdding, addVariableButton), "Add a variable"); + addFormItem(variableSelect, "select from an existing variable"); + addFormItem(textArea, "Existing Levels for the selected variable"); + addFormItem(new Div(levelAddingSample, levelAddingLevelValue, addLevelButton), + "Add a new level for the variable"); + + addVariableButton.addClickListener(e -> { + if (variableAdding.isEmpty()) { + return; + } + ExperimentReference experimentReference = new ExperimentReference(this.experimentId.value()); + confoundingVariableService.createConfoundingVariable(projectId, + experimentReference, + variableAdding.getValue()); + reloadVariables(experimentReference); + }); + addLevelButton.addClickListener(e -> { + if (levelAddingLevelValue.isEmpty()) { + return; + } + if (levelAddingSample.isEmpty()) { + return; + } + if (variableSelect.isEmpty()) { + return; + } + ExperimentReference experimentReference = new ExperimentReference(experimentId.value()); + confoundingVariableService.setVariableLevelForSample(projectId, + experimentReference, + new SampleReference(levelAddingSample.getValue().sampleId().value()), + variableSelect.getValue().id(), + levelAddingLevelValue.getValue()); + reloadVariables(experimentReference); + }); + + levelAddingSample.setRenderer(new TextRenderer<>(Sample::label)); + levelAddingSample.setItemLabelGenerator(Sample::label); + variableSelect.addValueChangeListener(it -> { + if (Objects.isNull(it.getValue()) || it.getValue().equals(variableSelect.getEmptyValue())) { + textArea.clear(); + return; + } + List confoundingVariableLevels = confoundingVariableService.listLevelsForVariable( + projectId, it.getValue() + .id()); + List sampleIds = confoundingVariableLevels.stream() + .map(ConfoundingVariableLevel::sample) + .distinct() + .map(SampleReference::id) + .map(SampleId::parse) + .toList(); + Map samples = sampleInformationService.retrieveSamplesByIds(sampleIds) + .stream() + .collect( + Collectors.toMap(o -> o.sampleId().value(), o -> o)); + List values = confoundingVariableLevels.stream() + .map(confoundingVariableLevel -> "%s has %s of %s".formatted( + samples.get(confoundingVariableLevel.sample().id()).label(), + it.getValue().variableName(), confoundingVariableLevel.level())).toList(); + textArea.setValue(String.join("\n", values)); + }); + } +} diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/demo/ComponentDemo.java b/user-interface/src/main/java/life/qbic/datamanager/views/demo/ComponentDemo.java index 9196e223f..0da7fe4c4 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/demo/ComponentDemo.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/demo/ComponentDemo.java @@ -21,9 +21,9 @@ import life.qbic.datamanager.views.general.dialog.InputValidation; import life.qbic.datamanager.views.general.dialog.UserInput; import life.qbic.datamanager.views.general.dialog.stepper.Step; -import life.qbic.datamanager.views.general.dialog.stepper.StepperDisplay; import life.qbic.datamanager.views.general.dialog.stepper.StepperDialog; import life.qbic.datamanager.views.general.dialog.stepper.StepperDialogFooter; +import life.qbic.datamanager.views.general.dialog.stepper.StepperDisplay; import life.qbic.datamanager.views.general.icon.IconFactory; import org.springframework.context.annotation.Profile; import org.springframework.lang.NonNull; @@ -155,11 +155,11 @@ private static Div stepperDialogShowCase(List steps, String dialogTitle) { private static List threeSteps() { List steps = new ArrayList<>(); - for (int step= 0; step < 3; step++) { + for (int step = 0; step < 3; step++) { int stepNumber = step + 1; steps.add(new Step() { - final ExampleUserInput userInput = new ExampleUserInput("example step " + stepNumber ); + final ExampleUserInput userInput = new ExampleUserInput("example step " + stepNumber); @Override diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/dialog/stepper/StepperDialogFooter.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/dialog/stepper/StepperDialogFooter.java index a9bf3cbb6..fa23cede5 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/general/dialog/stepper/StepperDialogFooter.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/dialog/stepper/StepperDialogFooter.java @@ -93,7 +93,6 @@ Div createLast(StepperDialog dialog) { private static class FirstFooter extends Div { - FirstFooter(StepperDialog dialog) { addClassNames(FLEX_HORIZONTAL, GAP_04, "footer"); var buttonFactory = new ButtonFactory(); diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/projects/overview/components/ProjectCollectionComponent.java b/user-interface/src/main/java/life/qbic/datamanager/views/projects/overview/components/ProjectCollectionComponent.java index ee375679d..1a2579aae 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/projects/overview/components/ProjectCollectionComponent.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/projects/overview/components/ProjectCollectionComponent.java @@ -201,7 +201,8 @@ public ProjectOverviewItem(ProjectOverview projectOverview) { Span header = createHeader(projectOverview.projectCode(), projectOverview.projectTitle()); add(header); Span lastModified = new Span( - String.format("Last modified on %s", DateTimeRendering.simple(projectOverview.lastModified()))); + String.format("Last modified on %s", + DateTimeRendering.simple(projectOverview.lastModified()))); lastModified.addClassName("tertiary"); add(lastModified); projectDetails.addClassName("details"); @@ -237,10 +238,10 @@ private Span createHeader(String projectCode, String projectTitle) { public void setMeasurementDependentTags() { tags.removeAll(); Collection measurementTypes = new ArrayList<>(); - if (projectOverview.pxpMeasurementCount() != null) { + if (projectOverview.pxpMeasurementCount() > 0) { measurementTypes.add(MeasurementType.PROTEOMICS); } - if (projectOverview.ngsMeasurementCount() != null) { + if (projectOverview.ngsMeasurementCount() > 0) { measurementTypes.add(MeasurementType.GENOMICS); } measurementTypes.forEach(measurementType -> { diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/info/ProjectSummaryComponent.java b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/info/ProjectSummaryComponent.java index ad8006b43..73816be7b 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/info/ProjectSummaryComponent.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/info/ProjectSummaryComponent.java @@ -19,9 +19,6 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,8 +34,8 @@ import life.qbic.datamanager.views.Context; import life.qbic.datamanager.views.TagFactory; import life.qbic.datamanager.views.account.UserAvatar.UserAvatarGroupItem; -import life.qbic.datamanager.views.general.DateTimeRendering; import life.qbic.datamanager.views.general.CollapsibleDetails; +import life.qbic.datamanager.views.general.DateTimeRendering; import life.qbic.datamanager.views.general.DetailBox; import life.qbic.datamanager.views.general.Heading; import life.qbic.datamanager.views.general.IconLabel; @@ -615,10 +612,10 @@ private Div createTags(ProjectOverview projectOverview) { private List buildTags(ProjectOverview projectInformation) { var tags = new ArrayList(); - if (projectInformation.ngsMeasurementCount() != null) { + if (projectInformation.ngsMeasurementCount() > 0) { tags.add(TagFactory.forMeasurement(GENOMICS)); } - if (projectInformation.pxpMeasurementCount() != null) { + if (projectInformation.pxpMeasurementCount() > 0) { tags.add(TagFactory.forMeasurement(PROTEOMICS)); } return tags; diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/SampleInformationMain.java b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/SampleInformationMain.java index 5d1cf853f..387d00b28 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/SampleInformationMain.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/SampleInformationMain.java @@ -43,11 +43,10 @@ import life.qbic.projectmanagement.application.DeletionService; import life.qbic.projectmanagement.application.ProjectInformationService; import life.qbic.projectmanagement.application.ProjectOverview; -import life.qbic.projectmanagement.application.batch.BatchRegistrationService; +import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference; import life.qbic.projectmanagement.application.experiment.ExperimentInformationService; import life.qbic.projectmanagement.application.sample.SampleInformationService; import life.qbic.projectmanagement.application.sample.SamplePreview; -import life.qbic.projectmanagement.application.sample.SampleRegistrationService; import life.qbic.projectmanagement.application.sample.SampleRegistrationServiceV2; import life.qbic.projectmanagement.application.sample.SampleValidationService; import life.qbic.projectmanagement.domain.model.batch.BatchId; @@ -84,24 +83,22 @@ public class SampleInformationMain extends Main implements BeforeEnterObserver { private final BatchDetailsComponent batchDetailsComponent; private final DownloadProvider metadataDownload; - private final SampleInformationXLSXProvider sampleInformationXLSXProvider; + private final transient SampleInformationXLSXProvider sampleInformationXLSXProvider; private final Div content = new Div(); private final TextField searchField = new TextField(); private final Disclaimer noGroupsDefinedDisclaimer; private final Disclaimer noSamplesRegisteredDisclaimer; - private final ProjectInformationService projectInformationService; - private final CancelConfirmationDialogFactory cancelConfirmationDialogFactory; - private final MessageSourceNotificationFactory messageSourceNotificationFactory; - private final SampleValidationService sampleValidationService; - private final TemplateService templateService; - private final SampleRegistrationServiceV2 sampleRegistrationServiceV2; + private final transient ProjectInformationService projectInformationService; + private final transient CancelConfirmationDialogFactory cancelConfirmationDialogFactory; + private final transient MessageSourceNotificationFactory messageSourceNotificationFactory; + private final transient SampleValidationService sampleValidationService; + private final transient TemplateService templateService; + private final transient SampleRegistrationServiceV2 sampleRegistrationServiceV2; private transient Context context; public SampleInformationMain(@Autowired ExperimentInformationService experimentInformationService, - @Autowired BatchRegistrationService batchRegistrationService, @Autowired DeletionService deletionService, - @Autowired SampleRegistrationService sampleRegistrationService, @Autowired SampleInformationService sampleInformationService, @Autowired SampleDetailsComponent sampleDetailsComponent, @Autowired BatchDetailsComponent batchDetailsComponent, @@ -232,7 +229,7 @@ private void onRegisterBatchClicked() { UI ui = event.getSource().getUI().orElseThrow(); CompletableFuture registrationTask = sampleRegistrationServiceV2.registerSamples( event.validatedSampleMetadata(), - projectId, event.batchName(), false, experimentId) + projectId, event.batchName(), false, new ExperimentReference(experimentId.value())) .orTimeout(5, TimeUnit.MINUTES); try { registrationTask @@ -366,7 +363,8 @@ private void onEditBatchClicked(EditBatchEvent editBatchEvent) { projectId, batchId, event.batchName(), - false) + false, + new ExperimentReference(context.experimentId().orElseThrow().value())) .orTimeout(5, TimeUnit.MINUTES); try { editTask diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/EditSampleBatchDialog.java b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/EditSampleBatchDialog.java index d015b1d82..0a944cce2 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/EditSampleBatchDialog.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/EditSampleBatchDialog.java @@ -161,6 +161,7 @@ private void onUploadSucceeded(SampleValidationService sampleValidationService, sampleInformationForExistingSample.analyte(), sampleInformationForExistingSample.analysisMethod(), sampleInformationForExistingSample.comment(), + sampleInformationForExistingSample.confoundingVariables(), experimentId, projectId ).orTimeout(1, TimeUnit.MINUTES); diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/RegisterSampleBatchDialog.java b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/RegisterSampleBatchDialog.java index cdea300bb..5800b6432 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/RegisterSampleBatchDialog.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/RegisterSampleBatchDialog.java @@ -137,6 +137,7 @@ private void onUploadSucceeded(SampleValidationService sampleValidationService, sampleInformationForNewSample.analyte(), sampleInformationForNewSample.analysisMethod(), sampleInformationForNewSample.comment(), + sampleInformationForNewSample.confoundingVariables(), experimentId, projectId ).orTimeout(1, TimeUnit.MINUTES); @@ -145,6 +146,7 @@ private void onUploadSucceeded(SampleValidationService sampleValidationService, var validationTasks = CompletableFuture //allOf makes sure exceptional state is transferred to outer completable future. .allOf(validations.toArray(new CompletableFuture[0])) + .orTimeout(5, TimeUnit.MINUTES) .thenApply(v -> validations.stream() .map(CompletableFuture::join) .toList()) diff --git a/user-interface/vite.config.ts b/user-interface/vite.config.ts index 4d6a0222e..03ef23c5d 100644 --- a/user-interface/vite.config.ts +++ b/user-interface/vite.config.ts @@ -1,5 +1,5 @@ -import { UserConfigFn } from 'vite'; -import { overrideVaadinConfig } from './vite.generated'; +import {UserConfigFn} from 'vite'; +import {overrideVaadinConfig} from './vite.generated'; const customConfig: UserConfigFn = (env) => ({ // Here you can add custom Vite parameters