Skip to content

Commit

Permalink
Merge pull request #341 from qbicsoftware/development
Browse files Browse the repository at this point in the history
* set duration to 3sec and change position (#333)

* Introduce editor functionality to show a custom component within the spreadsheet only if user selects the cell.  (#335)

* WIP increase performance

* Fix blinking issue for comboboxes

* Add javadoc and fix header row

* Fix Dialog loading

* Fix rowcount accession and prefilling of cells for validation

* Remove unnecessary duplicate unused checks since we already define the value during row addition

* WIP fix dialog reload everything else works

* Finally fixed!

* Remove unused method

* replaces red validation borders in spreadsheet by cell highlighting comparable to usual style

* Update Javadocs

* rename conversion methods

* Fix SonarCloud Code Smell

* unlock cells after validation

* Fix copy past JD fail

---------

Co-authored-by: wow-such-code <[email protected]>

* Confirm experimental group deletion with the user when editing experimental variables (#334)

* clean up code

* add double-check confirmation dialog

* address inspection warning

* extract confirm dialog into new method

* Fix typo

Co-authored-by: Steffengreiner <[email protected]>

* Fix switched JavaDocs

Co-authored-by: steffengreiner <[email protected]>

---------

Co-authored-by: Steffengreiner <[email protected]>

* Inform User that row deletion is not possible if only one row remains in the spreadsheet.  (#336)

* Ensure user can't delete the last row

* Inline method and ensure that the header row is never deleted

* Fix typo in notification

* Enables multiple experimental group addition (#328)

* Save current work progress

* Save work

* Save work

* Enable addition of multiple experimental groups

* Reload experimental groups after registration

* Enable experimental group edit

* Save current work

* Save the day

* Finish styling

* Clean up

* More cleanup work

* rename methods, extract methods

Co-authored-by: steffengreiner <[email protected]>

* Add all experimental groups or none

Co-authored-by: steffengreiner <[email protected]>

* rename method

Co-authored-by: steffengreiner <[email protected]>

* Exchange group input by existing component

* address code review

Co-authored-by: steffengreiner <[email protected]>

* Add JavaDoc

Co-authored-by: steffengreiner <[email protected]>

* rename method

Co-authored-by: steffengreiner <[email protected]>

* address review

* address review

* remove duplicate css, group css selectors

Co-authored-by: steffengreiner <[email protected]>

* remove unused css

Co-authored-by: steffengreiner <[email protected]>

---------

Co-authored-by: Tobias Koch <[email protected]>
Co-authored-by: steffengreiner <[email protected]>

* Adds prefill option to sample batch registration (#338)

* set experiment null on closing of dialog. reload after prefill to make prefill selection after back-button work

* Fix reload of cells with prefilled information

* Adapt JD

* only remove prefill info if it is variable, correctly validate and remove validation errors

* Extract reload into singular method

* remove unused boolean flags

* clear currently selected cell if experiment has changed

* implement correct sorting for replicate labels

---------

Co-authored-by: Steffengreiner <[email protected]>
Co-authored-by: Tobias Koch <[email protected]>

* Check if the cells contain valid values before validation (#337)

* Check if the cells contain valid values before validation

* add bracket missing due to merge

---------

Co-authored-by: Tobias Koch <[email protected]>
Co-authored-by: wow-such-code <[email protected]>

* Batch registration is enqueued propely with jobrunnr (#340)

* Fix missing replicate and specify job logging for add sample to batch job

* Fix Job scheduling

* Allow the user to edit experiment information via dialog  (#339)

* Provide edit functionality of an experiment

* Provide JD and ensure that only valid values land in backend

* Remove outdated dialog and propagate experiment creation correctly

* Remove unused deletion methods

* Remove unused css and update method name

* address Code smell

* address Code smell

* Address code review part 1

* Fix faulty renaming of method

* Extract methods from multiline lambda expression during experimentInformationDialog creation

* Fix CSS

* Propagate Experiment Information to components instead of reloading the page

---------

Co-authored-by: Tobias Koch <[email protected]>
Co-authored-by: wow-such-code <[email protected]>
Co-authored-by: Sven F <[email protected]>
  • Loading branch information
3 people authored Aug 8, 2023
2 parents bfe2a5c + 877b1c7 commit 5e5e2b3
Show file tree
Hide file tree
Showing 46 changed files with 1,933 additions and 1,308 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package life.qbic.projectmanagement.application;

import static java.util.Objects.*;
import static java.util.Objects.requireNonNull;
import static life.qbic.logging.service.LoggerFactory.logger;

import java.util.Objects;
import life.qbic.application.commons.Result;
import life.qbic.logging.api.Logger;
import life.qbic.projectmanagement.application.sample.SampleInformationService;
import life.qbic.projectmanagement.domain.project.experiment.ExperimentId;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -19,8 +20,8 @@
@Service
public class DeletionService {

private static final Logger log = logger(DeletionService.class);
private final ExperimentInformationService experimentInformationService;

private final SampleInformationService sampleInformationService;

@Autowired
Expand All @@ -47,13 +48,27 @@ public Result<ExperimentId, ResponseCode> deleteAllExperimentalVariables(Experim
if (queryResult.isError()) {
return Result.fromError(ResponseCode.QUERY_FAILED);
}
if (queryResult.isValue() && queryResult.getValue().size() > 0) {
if (queryResult.isValue() && !queryResult.getValue().isEmpty()) {
return Result.fromError(ResponseCode.SAMPLES_STILL_ATTACHED_TO_EXPERIMENT);
}
experimentInformationService.deleteAllExperimentalVariables(id);
return Result.fromValue(id);
}

public Result<ExperimentId, ResponseCode> deleteAllExperimentalGroups(ExperimentId id) {
var queryResult = sampleInformationService.retrieveSamplesForExperiment(id);
if (queryResult.isError()) {
log.debug("experiment (%s) converting %s to %s".formatted(id, queryResult.getError(),
ResponseCode.QUERY_FAILED));
return Result.fromError(ResponseCode.QUERY_FAILED);
}
if (queryResult.isValue() && !queryResult.getValue().isEmpty()) {
return Result.fromError(ResponseCode.SAMPLES_STILL_ATTACHED_TO_EXPERIMENT);
}
experimentInformationService.deleteAllExperimentalGroups(id);
return Result.fromValue(id);
}

public enum ResponseCode {
SAMPLES_STILL_ATTACHED_TO_EXPERIMENT, QUERY_FAILED
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import life.qbic.application.commons.ApplicationException;
import life.qbic.application.commons.Result;
import life.qbic.logging.api.Logger;
Expand Down Expand Up @@ -74,7 +73,7 @@ public Result<ExperimentalGroup, ResponseCode> addExperimentalGroupToExperiment(

Experiment activeExperiment = loadExperimentById(experimentId);
Result<ExperimentalGroup, ResponseCode> result = activeExperiment.addExperimentalGroup(
experimentalGroup.levels(), experimentalGroup.sampleSize());
experimentalGroup.levels(), experimentalGroup.replicateCount());
if (result.isValue()) {
experimentRepository.update(activeExperiment);
}
Expand All @@ -100,15 +99,9 @@ public List<ExperimentalGroup> experimentalGroupsFor(ExperimentId experimentId)
return experiment.getExperimentalGroups().stream().toList();
}

public void deleteExperimentGroup(ExperimentId experimentId, long groupId) {
Experiment experiment = loadExperimentById(experimentId);
experiment.removeExperimentGroup(groupId);
experimentRepository.update(experiment);
}

/**
* <b>ATTENTION!</b> This will remove all existing experimental variables and all defined experimental
* groups in a give experiment!
* <b>ATTENTION!</b> This will remove all existing experimental variables and all defined
* experimental groups in a give experiment!
*
* @param experimentId the experiment reference to delete the experimental variables from
* @since 1.0.0
Expand All @@ -119,6 +112,7 @@ public void deleteAllExperimentalVariables(ExperimentId experimentId) {
experiment.removeAllExperimentalVariables();
experimentRepository.update(experiment);
}

/**
* Returns a list of experiment for a given project.
*
Expand Down Expand Up @@ -261,19 +255,62 @@ public List<ExperimentalVariable> getVariablesOfExperiment(ExperimentId experime
}

/**
* Checks if the provided ExperimentId contains an experimental Group
* Deletes all experimental groups in a given experiment.
* <p>
* This method does not check if samples are already.
*
* @param experimentId the {@link ExperimentId} of the {@link Experiment} which should be checked
* if it contains an {@link ExperimentalGroup}
* @return a boolean indicating if the experiment contains an {@link ExperimentalGroup}
* @param id the experiment identifier of the experiment the experimental groups are going to be
* deleted.
* @since 1.0.0
*/
public boolean hasExperimentalGroup(ExperimentId experimentId) {
Experiment experiment = loadExperimentById(experimentId);
return !experiment.getExperimentalGroups().isEmpty();
public void deleteAllExperimentalGroups(ExperimentId id) {
Experiment experiment = loadExperimentById(id);
experiment.removeAllExperimentalGroups();
experimentRepository.update(experiment);
}

public record ExperimentalGroupDTO(Set<VariableLevel> levels, int sampleSize) {
/**
* Adds experimental groups to an experiment
*
* @param experimentId the experiment to add the groups to
* @param experimentalGroupDTOS the group information
* @return either the collection of added groups or an appropriate response code
*/
public Result<Collection<ExperimentalGroup>, ResponseCode> addExperimentalGroupsToExperiment(
ExperimentId experimentId, List<ExperimentalGroupDTO> experimentalGroupDTOS) {
Experiment experiment = loadExperimentById(experimentId);
List<ExperimentalGroup> addedGroups = new ArrayList<>();
for (ExperimentalGroupDTO experimentalGroupDTO : experimentalGroupDTOS) {
Result<ExperimentalGroup, ResponseCode> result = experiment.addExperimentalGroup(
experimentalGroupDTO.levels(),
experimentalGroupDTO.replicateCount());
if (result.isError()) {
return Result.fromError(result.getError());
} else {
addedGroups.add(result.getValue());
}
}
experimentRepository.update(experiment);
return Result.fromValue(addedGroups);
}

public void editExperimentInformation(ExperimentId experimentId, String experimentName,
List<Species> species, List<Specimen> specimens, List<Analyte> analytes) {
Experiment experiment = loadExperimentById(experimentId);
experiment.setName(experimentName);
experiment.setSpecies(species);
experiment.setAnalytes(analytes);
experiment.setSpecimens(specimens);
experimentRepository.update(experiment);
}

/**
* Information about an experimental group
*
* @param levels the levels in the condition of the group
* @param replicateCount the number of biological replicates
*/
public record ExperimentalGroupDTO(Collection<VariableLevel> levels, int replicateCount) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class SampleRegisteredPolicy {
/**
* Creates an instance of a {@link SampleRegisteredPolicy} object.
* <p>
* All directives will be created an subscribed upon instantiation.
* All directives will be created and subscribed upon instantiation.
*
* @param addSampleToBatch directive to update the affected sample
* {@link life.qbic.projectmanagement.domain.project.sample.Batch}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package life.qbic.projectmanagement.application.policy.directive;

import static life.qbic.logging.service.LoggerFactory.logger;

import life.qbic.domain.concepts.DomainEvent;
import life.qbic.domain.concepts.DomainEventSubscriber;
import life.qbic.logging.api.Logger;
import life.qbic.projectmanagement.application.batch.BatchRegistrationService;
import life.qbic.projectmanagement.domain.project.sample.BatchId;
import life.qbic.projectmanagement.domain.project.sample.SampleId;
import life.qbic.projectmanagement.domain.project.sample.event.SampleRegistered;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.scheduling.JobScheduler;

import org.springframework.stereotype.Component;
/**
* <b>Directive: Add Sample to Batch</b>
* <p>
Expand All @@ -16,8 +20,10 @@
*
* @since 1.0.0
*/
@Component
public class AddSampleToBatch implements DomainEventSubscriber<SampleRegistered> {

private static final Logger log = logger(AddSampleToBatch.class);
private final BatchRegistrationService batchRegistrationService;

private final JobScheduler jobScheduler;
Expand All @@ -38,7 +44,8 @@ public void handleEvent(SampleRegistered event) {
jobScheduler.enqueue(() -> addSampleToBatch(event.registeredSample(), event.assignedBatch()));
}

protected void addSampleToBatch(SampleId sample, BatchId batch) throws RuntimeException {
@Job(name = "Add_Sample_To_Batch")
public void addSampleToBatch(SampleId sample, BatchId batch) throws RuntimeException {
batchRegistrationService.addSampleToBatch(sample, batch).onError(responseCode -> {
throw new RuntimeException(
String.format("Adding sample %s to batch %s failed, response code was %s ", sample, batch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import life.qbic.projectmanagement.domain.project.ProjectId;
import life.qbic.projectmanagement.domain.project.event.ProjectRegisteredEvent;
import life.qbic.projectmanagement.domain.project.repository.ProjectRepository;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -50,6 +51,7 @@ public void handleEvent(ProjectRegisteredEvent event) {
jobScheduler.enqueue(() -> createSampleStatisticsEntry(event.createdProject()));
}

@Job(name = "Create_Sample_Statistics_Entry")
public void createSampleStatisticsEntry(String projectId) throws RuntimeException {
var id = ProjectId.parse(projectId);
if (sampleStatisticsEntryMissing(id)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Result<Collection<Sample>, ResponseCode> registerSamples(
Collection<SampleRegistrationRequest> sampleRegistrationRequests, ProjectId projectId) {
Objects.requireNonNull(sampleRegistrationRequests);
Objects.requireNonNull(projectId);
if (sampleRegistrationRequests.size() < 1) {
if (sampleRegistrationRequests.isEmpty()) {
return Result.fromError(ResponseCode.NO_SAMPLES_DEFINED);
}
Map<SampleCode, SampleRegistrationRequest> sampleCodesToRegistrationRequests = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.persistence.Entity;
import java.io.Serial;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Objects;

/**
Expand Down Expand Up @@ -102,4 +103,22 @@ public boolean equals(Object o) {
public String toString() {
return "BiologicalReplicate{" + "id=" + id + ", label='" + label + '\'' + '}';
}

/**
* Provides sorting functionality for labels ending in numbers, e.g. label1 < label2 < label10.
* This is based on label length and only works for labels starting with the same letters.
*/
public static class LexicographicLabelComparator implements Comparator<BiologicalReplicate> {

@Override
public int compare(BiologicalReplicate r1, BiologicalReplicate r2) {
int l1 = r1.label.length();
int l2 = r2.label.length();
if (l1 == l2) {
return r1.label.compareTo(r2.label);
} else {
return l1-l2;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
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.projectmanagement.domain.project.experiment.ExperimentalDesign.AddExperimentalGroupResponse.ResponseCode;
import life.qbic.projectmanagement.domain.project.experiment.exception.ConditionExistsException;
Expand Down Expand Up @@ -40,6 +42,7 @@ public class Experiment {

@Embedded
private ExperimentalDesign experimentalDesign;

@ElementCollection(targetClass = Analyte.class)
private List<Analyte> analytes = new ArrayList<>();
@ElementCollection(targetClass = Species.class)
Expand Down Expand Up @@ -259,4 +262,50 @@ public List<ExperimentalGroup> getExperimentalGroups() {
public void removeExperimentGroup(long groupId) {
experimentalDesign.removeExperimentalGroup(groupId);
}

/**
* Sets the name of the experiment.
*/
public void setName(String name) {
if (name.isEmpty()) {
throw new ApplicationException("An Experiment must have a name");
}
this.name = name;
}

/**
* Sets the list of {@link Species}for an experiment.
*/
public void setSpecies(
List<Species> species) {
if (species == null || species.isEmpty()) {
throw new ApplicationException(ErrorCode.NO_SPECIES_DEFINED,
ErrorParameters.of(species));
}
this.species = species;
}

/**
* Sets the list of {@link Species} for an experiment.
*/
public void setSpecimens(
List<Specimen> specimens) {
if (specimens == null || specimens.isEmpty()) {
throw new ApplicationException(ErrorCode.NO_SPECIMEN_DEFINED,
ErrorParameters.of(specimens));
}
this.specimens = specimens;
}

/**
* Sets the list of {@link Analyte} for an experiment.
*/
public void setAnalytes(
List<Analyte> analytes) {
if (analytes == null || analytes.isEmpty()) {
throw new ApplicationException(ErrorCode.NO_ANALYTE_DEFINED,
ErrorParameters.of(analytes));
}
this.analytes = analytes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ boolean isConditionDefined(Condition condition) {
}

Result<VariableName, Exception> addVariable(String variableName, List<ExperimentalValue> levels) {
if (levels.size() < 1) {
if (levels.isEmpty()) {
return Result.fromError(new IllegalArgumentException(
"No levels were defined for " + variableName));
}
Expand All @@ -197,7 +197,7 @@ Result<VariableName, Exception> addVariable(String variableName, List<Experiment
}

public void removeAllExperimentalVariables() throws IllegalStateException {
if (experimentalGroups.size() > 0) {
if (!experimentalGroups.isEmpty()) {
throw new IllegalStateException("Cannot delete experimental variables referenced by an experimental group.");
}
this.variables.clear();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package life.qbic.projectmanagement.domain.project.sample;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand All @@ -21,6 +24,7 @@
public class Batch {

@EmbeddedId
@Column(name = "id")
private BatchId id;

@Column(name = "batchLabel")
Expand All @@ -29,7 +33,8 @@ public class Batch {
@Column(name = "isPilot")
private boolean pilot;

@ElementCollection(targetClass = SampleId.class)
@ElementCollection(targetClass = SampleId.class, fetch = FetchType.EAGER)
@CollectionTable(name = "sample_batches_sampleid", joinColumns = @JoinColumn(name = "batch_id"))
private List<SampleId> sampleIds;

protected Batch() {
Expand Down
Loading

0 comments on commit 5e5e2b3

Please sign in to comment.