+ *
+ * @since
+ */
+public interface ExampleProvider {
+
+ record Helper(String exampleValue, String description) {
+
+ }
+
+ Helper getHelper(Column column);
+
+}
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 0e7993cf9..9b00aef0b 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
@@ -132,7 +132,7 @@ private List convertNewProteomicsMeasurement(ParsingResult
var technicalReplicateName = parsingResult.getValueOrDefault(i,
ProteomicsMeasurementRegisterColumn.TECHNICAL_REPLICATE_NAME.headerName(), "");
var organisationId = parsingResult.getValueOrDefault(i,
- ProteomicsMeasurementRegisterColumn.ORGANISATION_ID.headerName(), "");
+ ProteomicsMeasurementRegisterColumn.ORGANISATION_URL.headerName(), "");
var msDevice = parsingResult.getValueOrDefault(i,
ProteomicsMeasurementRegisterColumn.MS_DEVICE.headerName(), "");
var samplePoolGroup = parsingResult.getValueOrDefault(i,
@@ -192,7 +192,7 @@ private List convertExistingProteomicsMeasurement(
var technicalReplicateName = parsingResult.getValueOrDefault(i,
ProteomicsMeasurementEditColumn.TECHNICAL_REPLICATE_NAME.headerName(), "");
var organisationId = parsingResult.getValueOrDefault(i,
- ProteomicsMeasurementEditColumn.ORGANISATION_ID.headerName(), "");
+ ProteomicsMeasurementEditColumn.ORGANISATION_URL.headerName(), "");
var msDevice = parsingResult.getValueOrDefault(i,
ProteomicsMeasurementEditColumn.MS_DEVICE.headerName(), "");
var samplePoolGroup = parsingResult.getValueOrDefault(i,
@@ -252,7 +252,7 @@ private List convertExistingNGSMeasurement(ParsingResult pa
""))
);
var organisationId = parsingResult.getValueOrDefault(i,
- NGSMeasurementEditColumn.ORGANISATION_ID.headerName(), "");
+ NGSMeasurementEditColumn.ORGANISATION_URL.headerName(), "");
var instrument = parsingResult.getValueOrDefault(i,
NGSMeasurementEditColumn.INSTRUMENT.headerName(), "");
var facility = parsingResult.getValueOrDefault(i,
@@ -304,7 +304,7 @@ private List convertNewNGSMeasurement(ParsingResult parsing
""))
);
var organisationId = parsingResult.getValueOrDefault(i,
- NGSMeasurementRegisterColumn.ORGANISATION_ID.headerName(), "");
+ NGSMeasurementRegisterColumn.ORGANISATION_URL.headerName(), "");
var instrument = parsingResult.getValueOrDefault(i,
NGSMeasurementRegisterColumn.INSTRUMENT.headerName(), "");
var facility = parsingResult.getValueOrDefault(i,
diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementEditColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementEditColumn.java
index 5fef50044..36c276085 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementEditColumn.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementEditColumn.java
@@ -1,6 +1,10 @@
package life.qbic.datamanager.parser.measurement;
import java.util.Arrays;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
/**
* NGS Measurement Columns
@@ -10,13 +14,13 @@
* column index and if the column should be set to readOnly in the generated sheet
*
*/
-public enum NGSMeasurementEditColumn {
+public enum NGSMeasurementEditColumn implements Column {
MEASUREMENT_ID("Measurement ID", 0, true, true),
SAMPLE_ID("QBiC Sample Id", 1, true, true),
SAMPLE_NAME("Sample Name", 2, true, false),
POOL_GROUP("Sample Pool Group", 3, true, false),
- ORGANISATION_ID("Organisation ID", 4, false, true),
+ ORGANISATION_URL("Organisation URL", 4, false, true),
ORGANISATION_NAME("Organisation Name", 5, true, false),
FACILITY("Facility", 6, false, true),
INSTRUMENT("Instrument", 7, false, true),
@@ -30,15 +34,67 @@ public enum NGSMeasurementEditColumn {
COMMENT("Comment", 15, false, false),
;
+
+ private static ExampleProvider exampleProvider = (Column column) -> {
+
+ if (column instanceof NGSMeasurementEditColumn ngsMeasurementEditColumn) {
+ return switch (ngsMeasurementEditColumn) {
+ case MEASUREMENT_ID -> new Helper("QBiC Measurement ID",
+ "A unique identifier of the measurement that will be linked to each sample.");
+ case SAMPLE_ID -> new Helper("QBiC sample IDs, e.g. Q2001, Q2002",
+ "The sample(s) that will be linked to the measurement.");
+ case SAMPLE_NAME -> new Helper("Free text, e.g. RNA Sample 1, RNA Sample 2",
+ "A visual aid to simplify sample navigation for the person managing the metadata.");
+ case POOL_GROUP -> new Helper("Free text, e.g. pool group 1",
+ "A group of samples that are pooled together for a measurement. All samples in a pool group should have the same label.");
+ case ORGANISATION_URL -> new Helper("ROR URL, e.g. https://ror.org/03a1kwz48", """
+ A unique identifier of the organisation where the measurement has been conducted.
+ Tip: You can click on the column header (%s) to go to the ROR registry website where you can search your organisation and find its ROR URL.
+ """.formatted(ORGANISATION_URL.headerName()));
+ case ORGANISATION_NAME -> new Helper("Free text, e.g. University of Tübingen",
+ "The name of the organisation where the measurement has been conducted.");
+ case FACILITY -> new Helper("Free text, e.g. Quantitative Biology Centre",
+ "The facilities name within the organisation (group name, etc.)");
+ case INSTRUMENT -> new Helper("CURIE (ontology), e.g. EFO:0008637", """
+ The instrument that has been used for the measurement.
+ We expect an ontology term CURIE.
+ Tip: You can click on the column header (%s) to go to the Data Manager where you can use our Ontology Search to query the CURIE for your instrument.
+ """.formatted(INSTRUMENT.headerName()));
+ case INSTRUMENT_NAME -> new Helper("Free text, e.g. Illumina HiSeq",
+ "The name of the instrument model that has been used for the measurement.");
+ case SEQUENCING_READ_TYPE -> new Helper("Free text, e.g. paired-end",
+ "The sequencing read type used to generate the sequence data.");
+ case LIBRARY_KIT -> new Helper("Free text, e.g. NEBNext Ultra II Directional RNA mRNA UMI",
+ "Provides important information for downstream analysis data use that is usually required for troubleshooting.");
+ case FLOW_CELL ->
+ new Helper("Free text, e.g. S4", "The flow cell type used for sequencing.");
+ case SEQUENCING_RUN_PROTOCOL -> new Helper("Free text, e.g. 104+19+10+104",
+ "Information on how many cycles for each read and index.");
+ case INDEX_I7 -> new Helper("Free text, e.g. NEBNext UDI UMI Set 1 B12 S789",
+ "Index used for multiplexing.");
+ case INDEX_I5 -> new Helper("Free text, e.g. NEBNext UDI UMI Set 1 B12 S579",
+ "Index used for multiplexing.");
+ case COMMENT ->
+ new Helper("Free text", "Notes about the measurement. (Max 500 characters)");
+ };
+ } else {
+ throw new IllegalArgumentException(
+ "Column not of class " + NGSMeasurementEditColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ }
+ };
private final String headerName;
private final int columnIndex;
private final boolean readOnly;
private final boolean mandatory;
+ @Override
+ public Optional getFillHelp() {
+ return Optional.ofNullable(exampleProvider.getHelper(this));
+ }
+
static int maxColumnIndex() {
- return Arrays.stream(values())
- .mapToInt(NGSMeasurementEditColumn::columnIndex)
- .max().orElse(0);
+ return Arrays.stream(values()).mapToInt(NGSMeasurementEditColumn::columnIndex).max().orElse(0);
}
/**
diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementRegisterColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementRegisterColumn.java
index 283e91f3a..a17af28c0 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementRegisterColumn.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/NGSMeasurementRegisterColumn.java
@@ -1,6 +1,10 @@
package life.qbic.datamanager.parser.measurement;
import java.util.Arrays;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
/**
* NGS Measurement Columns
@@ -10,21 +14,21 @@
* column index and if the column should be set to readOnly in the generated sheet
*
*/
-public enum NGSMeasurementRegisterColumn {
+public enum NGSMeasurementRegisterColumn implements Column {
SAMPLE_ID("QBiC Sample Id", 0, false, true),
- SAMPLE_NAME("Sample Name", 1, false, false),
+ SAMPLE_NAME("Sample Name", 1, true, false),
POOL_GROUP("Sample Pool Group", 2, false, false),
- ORGANISATION_ID("Organisation ID", 3, false, true),
+ ORGANISATION_URL("Organisation URL", 3, false, true),
FACILITY("Facility", 4, false, true),
INSTRUMENT("Instrument", 5, false, true),
SEQUENCING_READ_TYPE("Sequencing Read Type", 6, false, true),
LIBRARY_KIT("Library Kit", 7, false, false),
FLOW_CELL("Flow Cell", 8, false, false),
- SEQUENCING_RUN_PROTOCOL("Sequencing Run Protocol", 11, false, false),
- INDEX_I7("Index i7", 9, false, false),
- INDEX_I5("Index i5", 10, false, false),
- COMMENT("Comment", 11, false, false),
+ SEQUENCING_RUN_PROTOCOL("Sequencing Run Protocol", 9, false, false),
+ INDEX_I7("Index i7", 10, false, false),
+ INDEX_I5("Index i5", 11, false, false),
+ COMMENT("Comment", 12, false, false),
;
private final String headerName;
@@ -32,6 +36,48 @@ public enum NGSMeasurementRegisterColumn {
private final boolean readOnly;
private final boolean mandatory;
+ private static ExampleProvider exampleProvider = (Column column) -> {
+ if (column instanceof NGSMeasurementRegisterColumn ngsMeasurementRegisterColumn) {
+ return switch (ngsMeasurementRegisterColumn) {
+ case SAMPLE_ID -> new Helper("QBiC sample IDs, e.g. Q2001, Q2002",
+ "The sample(s) that will be linked to the measurement.");
+ case SAMPLE_NAME -> new Helper("Free text, e.g. RNA Sample 1, RNA Sample 2",
+ "A visual aid to simplify sample navigation for the person managing the metadata. Is ignored after upload.");
+ case POOL_GROUP -> new Helper("Free text, e.g. pool group 1",
+ "A group of samples that are pooled together for a measurement. All samples in a pool group should have the same label.");
+ case ORGANISATION_URL -> new Helper("ROR URL, e.g. https://ror.org/03a1kwz48", """
+ A unique identifier of the organisation where the measurement has been conducted.
+ Tip: You can click on the column header (%s) to go to the ROR registry website where you can search your organisation and find its ROR URL.
+ """.formatted(ORGANISATION_URL.headerName()));
+ case FACILITY -> new Helper("Free text, e.g. Quantitative Biology Centre",
+ "The facilities name within the organisation (group name, etc.)");
+ case INSTRUMENT -> new Helper("CURIE (ontology), e.g. EFO:0008637", """
+ The instrument that has been used for the measurement.
+ We expect an ontology term CURIE.
+ Tip: You can click on the column header (%s) to go to the Data Manager where you can use our Ontology Search to query the CURIE for your instrument.
+ """.formatted(INSTRUMENT.headerName()));
+ case SEQUENCING_READ_TYPE -> new Helper("Free text, e.g. paired-end",
+ "The sequencing read type used to generate the sequence data.");
+ case LIBRARY_KIT -> new Helper("Free text, e.g. NEBNext Ultra II Directional RNA mRNA UMI",
+ "Provides important information for downstream analysis data use that is usually required for troubleshooting.");
+ case FLOW_CELL ->
+ new Helper("Free text, e.g. S4", "The flow cell type used for sequencing.");
+ case SEQUENCING_RUN_PROTOCOL -> new Helper("Free text, e.g. 104+19+10+104",
+ "Information on how many cycles for each read and index.");
+ case INDEX_I7 -> new Helper("Free text, e.g. NEBNext UDI UMI Set 1 B12 S789",
+ "Index used for multiplexing.");
+ case INDEX_I5 -> new Helper("Free text, e.g. NEBNext UDI UMI Set 1 B12 S579",
+ "Index used for multiplexing.");
+ case COMMENT ->
+ new Helper("Free text", "Notes about the measurement. (Max 500 characters)");
+ };
+ } else {
+ throw new IllegalArgumentException(
+ "Column not of class " + NGSMeasurementRegisterColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ }
+ };
+
static int maxColumnIndex() {
return Arrays.stream(values())
.mapToInt(NGSMeasurementRegisterColumn::columnIndex)
@@ -60,11 +106,16 @@ public int columnIndex() {
return columnIndex;
}
- public boolean readOnly() {
+ public boolean isReadOnly() {
return readOnly;
}
public boolean isMandatory() {
return mandatory;
}
+
+ @Override
+ public Optional getFillHelp() {
+ return Optional.ofNullable(exampleProvider.getHelper(this));
+ }
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementEditColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementEditColumn.java
index db45d76b8..46d942b78 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementEditColumn.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementEditColumn.java
@@ -1,5 +1,10 @@
package life.qbic.datamanager.parser.measurement;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
+
/**
* NGS Measurement Columns
*
@@ -8,7 +13,7 @@
* column index and if the column should be set to readOnly in the generated sheet
*
*/
-public enum ProteomicsMeasurementEditColumn {
+public enum ProteomicsMeasurementEditColumn implements Column {
MEASUREMENT_ID("Measurement ID", 0, true, true),
SAMPLE_ID("QBiC Sample Id", 1, true, true),
@@ -16,7 +21,7 @@ public enum ProteomicsMeasurementEditColumn {
"Sample Name", 2, true, false),
POOL_GROUP("Sample Pool Group", 3, true, false),
TECHNICAL_REPLICATE_NAME("Technical Replicate", 4, false, false),
- ORGANISATION_ID("Organisation ID", 5, false, true),
+ ORGANISATION_URL("Organisation URL", 5, false, true),
ORGANISATION_NAME("Organisation Name", 6, true, false),
FACILITY("Facility", 7, false, true),
MS_DEVICE("MS Device", 8, false, true),
@@ -36,6 +41,62 @@ public enum ProteomicsMeasurementEditColumn {
private final int columnIndex;
private final boolean readOnly;
private final boolean mandatory;
+ private static final ExampleProvider exampleProvider = (Column column) ->
+ {
+ if (column instanceof ProteomicsMeasurementEditColumn proteomicsMeasurementEditColumn) {
+ return switch (proteomicsMeasurementEditColumn) {
+ case MEASUREMENT_ID -> new Helper("QBiC Measurement ID",
+ "A unique identifier of the measurement that will be linked to each sample.");
+ case SAMPLE_ID -> new Helper("QBiC sample IDs, e.g. Q29866",
+ "The sample(s) that will be linked to the measurement.");
+ case SAMPLE_NAME -> new Helper("Free text, e.g. MySample 01",
+ "A visual aid to simplify sample navigation for the person managing the metadata. Will be ignored after upload.");
+ case POOL_GROUP -> new Helper("Free text, e.g. pool group 1",
+ "A group of samples that are pooled together for a measurement. All samples in a pool group should have the same label.");
+ case TECHNICAL_REPLICATE_NAME -> new Helper("Free text, e.g. Sample 1A, Sample 1B",
+ "Repeated measurements of the same sample that represent independent measures of the random noise associated with protocols or equipment.");
+ case ORGANISATION_URL -> new Helper("ROR URL, e.g. https://ror.org/03a1kwz48", """
+ A unique identifier of the organisation where the measurement has been conducted.
+ Tip: You can click on the column header (%s) to go to the ROR registry website where you can search your organisation and find its ROR URL.
+ """.formatted(ORGANISATION_URL.headerName()));
+ case ORGANISATION_NAME -> new Helper("Free text, e.g. University of Tübingen",
+ "The name of the organisation where the measurement has been conducted.");
+ case FACILITY -> new Helper("Free text, e.g. Quantitative Biology Center",
+ "The facility's name within the organisation.");
+ case MS_DEVICE -> new Helper("CURIE (ontology), e.g. NCIT:C12434", """
+ The instrument that has been used for the measurement.
+ We expect an ontology term CURIE.
+ Tip: You can click on the column header (%s) to go to the Data Manager where you can use our Ontology Search to query the CURIE for your instrument.
+ """.formatted(MS_DEVICE.headerName()));
+ case MS_DEVICE_NAME -> new Helper("Free text, e.g. Illumina HiSeq",
+ "The name of the MS device model that has been used for the measurement.");
+ case CYCLE_FRACTION_NAME -> new Helper("Free text, e.g. Fraction01, AB",
+ "Sometimes a sample is fractionated and all fractions are measured. With this property you can indicate which fraction it is.");
+ case DIGESTION_METHOD -> new Helper("Enumeration, Select a value from the dropdown",
+ "Method that has been used to break proteins into peptides. Please use the dropdown menu to select one of the values.");
+ case DIGESTION_ENZYME -> new Helper("Free text, e.g. Trypsin, Chymotrypsin",
+ "Information about the enzymes used for the proteolytic.");
+ case ENRICHMENT_METHOD -> new Helper("Free text, e.g. Phosphopeptide Enrichment",
+ "Enrichment of proteins or peptides of different characteristics.");
+ case INJECTION_VOLUME -> new Helper("Whole number, e.g. 1, 6, 8",
+ "The sample volume injected into the LC column in microliter.");
+ case LC_COLUMN -> new Helper("Free text, can be a commercial name or brand",
+ "The type of column that has been used.");
+ case LCMS_METHOD -> new Helper("Free text",
+ "Laboratory specific methods that have been used for LCMS measurement.");
+ case LABELING_TYPE -> new Helper("Free text, e.g. Dimethyl, SILAC",
+ "The label type that has been used to label the sample for measurement.");
+ case LABEL -> new Helper("Free text, e.g. Light, Medium, Heavy",
+ "The label value for the label type that has been used.");
+ case COMMENT ->
+ new Helper("Free text", "Notes about the measurement. (Max 500 characters)");
+ };
+ } else {
+ throw new IllegalArgumentException(
+ "Column not of class " + NGSMeasurementEditColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ }
+ };
ProteomicsMeasurementEditColumn(String headerName, int columnIndex, boolean readOnly,
boolean mandatory) {
@@ -60,4 +121,9 @@ public boolean isReadOnly() {
public boolean isMandatory() {
return mandatory;
}
+
+ @Override
+ public Optional getFillHelp() {
+ return Optional.ofNullable(exampleProvider.getHelper(this));
+ }
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementRegisterColumn.java b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementRegisterColumn.java
index 12fdcaadb..8e78d11fc 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementRegisterColumn.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/parser/measurement/ProteomicsMeasurementRegisterColumn.java
@@ -1,22 +1,28 @@
package life.qbic.datamanager.parser.measurement;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
+
/**
- * TODO!
- * short description
- *
- *
Enumeration of the columns shown in the file used for proteomics measurement registration
+ * in the context of measurement file based upload. Provides the name of the header column, the
+ * column index and if the column should be set to readOnly in the generated sheet. Also provides
+ * information on whether the column is mandatory and can offer some help for filling it.
+ *
*/
-public enum ProteomicsMeasurementRegisterColumn {
+public enum ProteomicsMeasurementRegisterColumn implements Column {
- SAMPLE_ID("QBiC Sample Id", 0, true, true),
+ SAMPLE_ID("QBiC Sample Id", 0, false, true),
SAMPLE_NAME(
"Sample Name", 1, true, false),
- POOL_GROUP("Sample Pool Group", 2, true, false),
+ POOL_GROUP("Sample Pool Group", 2, false, false),
TECHNICAL_REPLICATE_NAME("Technical Replicate", 3, false, false),
CYCLE_FRACTION_NAME("Cycle/Fraction Name", 4, false, false),
- ORGANISATION_ID("Organisation ID", 5, false, true),
+ ORGANISATION_URL("Organisation URL", 5, false, true),
FACILITY("Facility", 6, false, true),
LC_COLUMN("LC Column", 7, false, true),
MS_DEVICE("MS Device", 8, false, true),
@@ -33,6 +39,55 @@ public enum ProteomicsMeasurementRegisterColumn {
private final int columnIndex;
private final boolean readOnly;
private final boolean mandatory;
+ private static final ExampleProvider exampleProvider = (Column column) -> {
+ if (column instanceof ProteomicsMeasurementRegisterColumn proteomicsMeasurementRegisterColumn) {
+ return switch (proteomicsMeasurementRegisterColumn) {
+ case SAMPLE_ID -> new Helper("QBiC sample IDs, e.g. Q29866",
+ "The sample(s) that will be linked to the measurement.");
+ case SAMPLE_NAME -> new Helper("Free text, e.g. MySample 01",
+ "A visual aid to simplify sample navigation for the person managing the metadata. Will be ignored after upload.");
+ case POOL_GROUP -> new Helper("Free text, e.g. pool group 1",
+ "A group of samples that are pooled together for a measurement. All samples in a pool group should have the same label.");
+ case TECHNICAL_REPLICATE_NAME -> new Helper("Free text, e.g. Sample 1A, Sample 1B",
+ "Repeated measurements of the same sample that represent independent measures of the random noise associated with protocols or equipment.");
+ case ORGANISATION_URL -> new Helper("ROR URL, e.g. https://ror.org/03a1kwz48", """
+ A unique identifier of the organisation where the measurement has been conducted.
+ Tip: You can click on the column header (%s) to go to the ROR registry website where you can search your organisation and find its ROR URL.
+ """.formatted(ORGANISATION_URL.headerName()));
+ case FACILITY -> new Helper("Free text, e.g. Quantitative Biology Center",
+ "The facility's name within the organisation.");
+ case MS_DEVICE -> new Helper("CURIE (ontology), e.g. NCIT:C12434", """
+ The instrument that has been used for the measurement.
+ We expect an ontology term CURIE.
+ Tip: You can click on the column header (%s) to go to the Data Manager where you can use our Ontology Search to query the CURIE for your instrument.
+ """.formatted(MS_DEVICE.headerName()));
+ case CYCLE_FRACTION_NAME -> new Helper("Free text, e.g. Fraction01, AB",
+ "Sometimes a sample is fractionated and all fractions are measured. With this property you can indicate which fraction it is.");
+ case DIGESTION_METHOD -> new Helper("Enumeration, Select a value from the dropdown",
+ "Method that has been used to break proteins into peptides. Please use the dropdown menu to select one of the values.");
+ case DIGESTION_ENZYME -> new Helper("Free text, e.g. Trypsin, Chymotrypsin",
+ "Information about the enzymes used for the proteolytic.");
+ case ENRICHMENT_METHOD -> new Helper("Free text, e.g. Phosphopeptide Enrichment",
+ "Enrichment of proteins or peptides of different characteristics.");
+ case INJECTION_VOLUME -> new Helper("Whole number, e.g. 1,6,8",
+ "The sample volume injected into the LC column in microliter.");
+ case LC_COLUMN -> new Helper("Free text, can be a commercial name or brand",
+ "The type of column that has been used.");
+ case LCMS_METHOD -> new Helper("Free text",
+ "Laboratory specific methods that have been used for LCMS measurement.");
+ case LABELING_TYPE -> new Helper("Free text, e.g. Dimethyl, SILAC",
+ "The label type that has been used to label the sample for measurement.");
+ case LABEL -> new Helper("Free text, e.g. Light, Medium, Heavy",
+ "The label value for the label type that has been used.");
+ case COMMENT ->
+ new Helper("Free text", "Notes about the measurement. (Max 500 characters)");
+ };
+ } else {
+ throw new IllegalArgumentException(
+ "Column not of class " + NGSMeasurementRegisterColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ }
+ };
ProteomicsMeasurementRegisterColumn(String headerName, int columnIndex, boolean readOnly,
boolean mandatory) {
@@ -57,4 +112,9 @@ public boolean isReadOnly() {
public boolean isMandatory() {
return mandatory;
}
+
+ @Override
+ public Optional getFillHelp() {
+ return Optional.ofNullable(exampleProvider.getHelper(this));
+ }
}
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 a562e8084..ae3aebaef 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
@@ -1,6 +1,10 @@
package life.qbic.datamanager.parser.sample;
import java.util.Arrays;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
/**
* Sample Edit Columns
@@ -10,17 +14,48 @@
* column index and if the column should be set to readOnly in the generated sheet
*
*/
-public enum EditColumn {
+public enum EditColumn implements Column {
SAMPLE_ID("QBiC Sample Id", 0, true, true),
SAMPLE_NAME("Sample Name", 1, false, true),
ANALYSIS("Analysis to be performed", 2, false, true),
BIOLOGICAL_REPLICATE("Biological Replicate", 3, false, false),
CONDITION("Condition", 4, false, true),
SPECIES("Species", 5, false, true),
- ANALYTE("Analyte", 6, false, true),
- SPECIMEN("Specimen", 7, false, true),
+ SPECIMEN("Specimen", 6, false, true),
+ ANALYTE("Analyte", 7, false, true),
COMMENT("Comment", 8, false, false);
+ private static final ExampleProvider exampleProvider = column -> {
+ if (column instanceof EditColumn editColumn) {
+ return switch (editColumn) {
+ case SAMPLE_ID -> new Helper("QBiC sample IDs, e.g. Q2001, Q2002",
+ "The sample(s) that will be linked to the measurement.");
+ case SAMPLE_NAME -> new Helper("Free text, e.g. RNA Sample 1, RNA Sample 2",
+ "A visual aid to simplify navigation for the person managing the metadata.");
+ case ANALYSIS -> new Helper("Enumeration, Select a value from the dropdown",
+ "The test performed on samples for the purpose of finding and measuring chemical substances.");
+ case BIOLOGICAL_REPLICATE -> new Helper("Free text, e.g. patient1, patient2, Mouse1", """
+ Different samples measured accross multiple conditions.
+ Tip: You can use this column to identifiy whether the samples belong to the same source.""");
+ case CONDITION -> new Helper("Enumeration, Select a value from the dropdown", """
+ A distinct value or condition of the independent variable at which the dependent variable is measured in order to carry out statistical analysis.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case SPECIES -> new Helper("Enumeration, Select a value from the dropdown", """
+ Scientific name of the organism(s) from which the biological material is derived. E.g. Homo sapiens, Mus musculus.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case ANALYTE -> new Helper("Enumeration, Select a value from the dropdown", """
+ The chemical substance extracted from the biological material that is identified and measured.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case SPECIMEN -> new Helper("Enumeration, Select a value from the dropdown", """
+ Name of the biological material from which the analytes would be extracted.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case COMMENT -> new Helper("Free text", "Notes about the sample. (Max 500 characters)");
+ };
+ }
+ throw new IllegalArgumentException(
+ "Column not of class " + EditColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ };
private final String headerName;
private final int columnIndex;
private final boolean readOnly;
@@ -61,4 +96,8 @@ public boolean isMandatory() {
return mandatory;
}
+ @Override
+ public Optional getFillHelp() {
+ return Optional.ofNullable(exampleProvider.getHelper(this));
+ }
}
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 ec7c400d2..524b07b8e 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
@@ -1,6 +1,10 @@
package life.qbic.datamanager.parser.sample;
import java.util.Arrays;
+import java.util.Optional;
+import life.qbic.datamanager.parser.Column;
+import life.qbic.datamanager.parser.ExampleProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
/**
* Sample Register Columns
@@ -10,17 +14,47 @@
* column index and if the column should be set to readOnly in the generated sheet
*
*/
-public enum RegisterColumn {
+public enum RegisterColumn implements Column {
SAMPLE_NAME("Sample Name", 0, false, true),
ANALYSIS("Analysis to be performed", 1, false, true),
BIOLOGICAL_REPLICATE("Biological Replicate", 2, false, false),
CONDITION("Condition", 3, false, true),
SPECIES("Species", 4, false, true),
- ANALYTE("Analyte", 5, false, true),
- SPECIMEN("Specimen", 6, false, true),
+ SPECIMEN("Specimen", 5, false, true),
+ ANALYTE("Analyte", 6, false, true),
COMMENT("Comment", 7, false, false);
+ private static final ExampleProvider exampleProvider = column -> {
+ if (column instanceof RegisterColumn registerColumn) {
+ return switch (registerColumn) {
+ case SAMPLE_NAME -> new Helper("Free text, e.g. RNA Sample 1, RNA Sample 2",
+ "A visual aid to simplify navigation for the person managing the metadata.");
+ case ANALYSIS -> new Helper("Enumeration, Select a value from the dropdown",
+ "The test performed on samples for the purpose of finding and measuring chemical substances.");
+ case BIOLOGICAL_REPLICATE -> new Helper("Free text, e.g. patient1, patient2, Mouse1", """
+ Different samples measured accross multiple conditions.
+ Tip: You can use this column to identifiy whether the samples belong to the same source.""");
+ case CONDITION -> new Helper("Enumeration, Select a value from the dropdown", """
+ A distinct value or condition of the independent variable at which the dependent variable is measured in order to carry out statistical analysis.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case SPECIES -> new Helper("Enumeration, Select a value from the dropdown", """
+ Scientific name of the organism(s) from which the biological material is derived. E.g. Homo sapiens, Mus musculus.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case ANALYTE -> new Helper("Enumeration, Select a value from the dropdown", """
+ The chemical substance extracted from the biological material that is identified and measured.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case SPECIMEN -> new Helper("Enumeration, Select a value from the dropdown", """
+ Name of the biological material from which the analytes would be extracted.
+ Note: The values in the dropdown are the predefined values from the experimental design.""");
+ case COMMENT -> new Helper("Free text", "Notes about the sample. (Max 500 characters)");
+ };
+ }
+ throw new IllegalArgumentException(
+ "Column not of class " + RegisterColumn.class.getName() + " but is "
+ + column.getClass().getName());
+ };
+
private final String headerName;
private final int columnIndex;
private final boolean readOnly;
@@ -60,4 +94,9 @@ public boolean isReadOnly() {
public boolean isMandatory() {
return mandatory;
}
+
+ @Override
+ public Optional getFillHelp() {
+ return Optional.of(exampleProvider.getHelper(this));
+ }
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/parser/xlsx/XLSXParser.java b/user-interface/src/main/java/life/qbic/datamanager/parser/xlsx/XLSXParser.java
index 6ec69b393..8b6794ed7 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/parser/xlsx/XLSXParser.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/parser/xlsx/XLSXParser.java
@@ -64,7 +64,7 @@ private static String readCellAsString(Cell cell) {
return switch (cell.getCellType()) {
case _NONE, ERROR, FORMULA, BLANK -> "";
case BOOLEAN -> Boolean.toString(cell.getBooleanCellValue());
- case NUMERIC -> String.valueOf(cell.getNumericCellValue());
+ case NUMERIC -> Double.toString(cell.getNumericCellValue());
case STRING -> cell.getStringCellValue();
};
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateDownloadFactory.java b/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateDownloadFactory.java
index 84491cdf6..5c7d3c485 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateDownloadFactory.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/templates/TemplateDownloadFactory.java
@@ -1,8 +1,8 @@
package life.qbic.datamanager.templates;
import life.qbic.datamanager.download.DownloadContentProvider;
-import life.qbic.datamanager.templates.measurement.NGSMeasurementTemplate;
-import life.qbic.datamanager.templates.measurement.ProteomicsMeasurementTemplate;
+import life.qbic.datamanager.templates.measurement.NGSMeasurementRegisterTemplate;
+import life.qbic.datamanager.templates.measurement.ProteomicsMeasurementRegisterTemplate;
/**
* Template Download Factory
@@ -17,8 +17,8 @@ public class TemplateDownloadFactory {
public static Template provider(TemplateType templateType) {
return
switch (templateType) {
- case MS_MEASUREMENT -> new ProteomicsMeasurementTemplate();
- case NGS_MEASUREMENT -> new NGSMeasurementTemplate();
+ case MS_MEASUREMENT -> new ProteomicsMeasurementRegisterTemplate();
+ case NGS_MEASUREMENT -> new NGSMeasurementRegisterTemplate();
};
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/XLSXTemplateHelper.java b/user-interface/src/main/java/life/qbic/datamanager/templates/XLSXTemplateHelper.java
index b57e1eee2..08f333616 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/templates/XLSXTemplateHelper.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/templates/XLSXTemplateHelper.java
@@ -3,6 +3,7 @@
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -21,6 +22,7 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.SheetVisibility;
import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap;
@@ -40,7 +42,9 @@ public class XLSXTemplateHelper {
private static final Random RANDOM = new Random();
private static final byte[] DARK_GREY = {(byte) 119, (byte) 119, (byte) 119};
private static final byte[] LIGHT_GREY = {(byte) 220, (byte) 220, (byte) 220};
+ private static final byte[] LINK_BLUE = {(byte) 9, (byte) 105, (byte) 218};
private static final int COLUMN_MAX_WIDTH = 255;
+ private static final String PROPERTY_INFORMATION_SHEET_NAME = "Property Information";
protected XLSXTemplateHelper() {
//hide constructor as static methods only are used
@@ -157,17 +161,43 @@ public static CellStyle createBoldCellStyle(Workbook workbook) {
CellStyle boldStyle = workbook.createCellStyle();
Font fontBold = workbook.createFont();
fontBold.setBold(true);
+ fontBold.setFontName("Open Sans");
+ fontBold.setFontHeightInPoints((short) 12);
+
boldStyle.setFont(fontBold);
+ return boldStyle;
+ }
+
+ public static CellStyle createDefaultCellStyle(Workbook workbook) {
+ CellStyle boldStyle = workbook.createCellStyle();
+ Font fontBold = workbook.createFont();
+ fontBold.setFontName("Open Sans");
+ fontBold.setFontHeightInPoints((short) 12);
+ boldStyle.setFont(fontBold);
return boldStyle;
}
+ public static CellStyle createLinkHeaderCellStyle(Workbook workbook) {
+ CellStyle linkHeaderStyle = workbook.createCellStyle();
+ XSSFFont linkFont = (XSSFFont) workbook.createFont();
+ linkFont.setColor(new XSSFColor(LINK_BLUE, new DefaultIndexedColorMap()));
+ linkFont.setBold(true);
+ linkFont.setFontName("Open Sans");
+ linkFont.setFontHeightInPoints((short) 12);
+
+ linkHeaderStyle.setFont(linkFont);
+ return linkHeaderStyle;
+ }
+
public static CellStyle createReadOnlyCellStyle(Workbook workbook) {
CellStyle readOnlyStyle = workbook.createCellStyle();
readOnlyStyle.setFillForegroundColor(new XSSFColor(LIGHT_GREY, new DefaultIndexedColorMap()));
readOnlyStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
XSSFFont font = (XSSFFont) workbook.createFont();
font.setColor(new XSSFColor(DARK_GREY, new DefaultIndexedColorMap()));
+ font.setFontName("Open Sans");
+ font.setFontHeightInPoints((short) 12);
readOnlyStyle.setFont(font);
return readOnlyStyle;
}
@@ -180,6 +210,8 @@ public static CellStyle createReadOnlyHeaderCellStyle(Workbook workbook) {
XSSFFont fontHeader = (XSSFFont) workbook.createFont();
fontHeader.setBold(true);
fontHeader.setColor(new XSSFColor(DARK_GREY, new DefaultIndexedColorMap()));
+ fontHeader.setFontName("Open Sans");
+ fontHeader.setFontHeightInPoints((short) 12);
readOnlyHeaderStyle.setFont(fontHeader);
return readOnlyHeaderStyle;
}
@@ -262,6 +294,8 @@ protected static String toCamelCase(String input) {
* Adds data validation to an area in the spreadsheet. Requires the valid options to be set
* beforehand as a name. This can be done by using {@link #createOptionArea(Sheet, String, List)}
*
+ * Please note: There must not exist any data validation for the cell area provided.
+ *
* @param sheet the sheet in which the validation should be added
* @param startColIdx the start column of the validated values >= 0
* @param startRowIdx the start row of the validated values >= 0
@@ -276,17 +310,157 @@ public static void addDataValidation(Sheet sheet, int startColIdx, int startRowI
stopRowIdx,
startColIdx,
stopColIdx);
+
+ if (hasAnyDataValidation(sheet, startRowIdx, startColIdx, stopRowIdx, stopColIdx)) {
+ throw new IllegalStateException(
+ "Cannot add data validation as there is already a data validation present at "
+ + validatedCells.getCellRangeAddress(0).formatAsString(sheet.getSheetName(), true));
+ }
+
DataValidationHelper dataValidationHelper = sheet.getDataValidationHelper();
DataValidationConstraint formulaListConstraint = dataValidationHelper
.createFormulaListConstraint(allowedValues.getNameName());
- DataValidation validation = dataValidationHelper.createValidation(formulaListConstraint,
- validatedCells);
+
+ var validation = dataValidationHelper.createValidation(formulaListConstraint, validatedCells);
+
validation.setSuppressDropDownArrow(true); // shows dropdown if true
validation.setShowErrorBox(true);
validation.createErrorBox("Invalid choice", "Please select a value from the dropdown list.");
sheet.addValidationData(validation);
}
+ /**
+ * Adds a property information description to the workbook. Creates a sheet called
+ * {@link #PROPERTY_INFORMATION_SHEET_NAME} if it does not exist yet.
+ *
+ * @param workbook the workbook to take
+ * @param columnName the column name / property name to add information to
+ * @param isMandatory is filling the column mandatory?
+ * @param descriptionTitle allowed value type and example
+ * @param description the description of the input
+ * @param headerStyle the style used for headers in the property information sheet
+ */
+ public static void addPropertyInformation(Workbook workbook,
+ String columnName,
+ boolean isMandatory,
+ String descriptionTitle,
+ String description,
+ CellStyle defaultStyle,
+ CellStyle headerStyle) {
+ // add row with information
+ Sheet propertyInformationSheet = Optional
+ .ofNullable(workbook.getSheet(PROPERTY_INFORMATION_SHEET_NAME))
+ .orElseGet(() -> workbook.createSheet(PROPERTY_INFORMATION_SHEET_NAME));
+ int lastRowIdx = Math.max(propertyInformationSheet.getLastRowNum(), 0);
+ if (lastRowIdx == 0) {
+ //we do not have a header yet
+ Row row = getOrCreateRow(propertyInformationSheet, 0);
+ Cell propertyNameCell = getOrCreateCell(row, 0);
+ propertyNameCell.setCellStyle(headerStyle);
+ propertyNameCell.setCellValue("Property Name");
+
+ Cell provisionCell = getOrCreateCell(row, 1);
+ provisionCell.setCellStyle(headerStyle);
+ provisionCell.setCellValue("Provision");
+
+ Cell allowedValuesCell = getOrCreateCell(row, 2);
+ allowedValuesCell.setCellStyle(headerStyle);
+ allowedValuesCell.setCellValue("Allowed Values");
+
+ Cell descriptionCell = getOrCreateCell(row, 3);
+ descriptionCell.setCellStyle(headerStyle);
+ descriptionCell.setCellValue("Description");
+
+ }
+ lastRowIdx++;
+ Row row = getOrCreateRow(propertyInformationSheet, lastRowIdx);
+ Cell propertyNameCell = getOrCreateCell(row, 0);
+ propertyNameCell.setCellStyle(defaultStyle);
+ propertyNameCell.setCellValue(columnName);
+
+ Cell provisionCell = getOrCreateCell(row, 1);
+ provisionCell.setCellStyle(defaultStyle);
+ provisionCell.setCellValue(isMandatory ? "mandatory" : "optional");
+
+ Cell allowedValuesCell = getOrCreateCell(row, 2);
+ allowedValuesCell.setCellStyle(defaultStyle);
+ allowedValuesCell.setCellValue(descriptionTitle);
+
+ Cell descriptionCell = getOrCreateCell(row, 3);
+ descriptionCell.setCellStyle(defaultStyle);
+ descriptionCell.setCellValue(description);
+
+ setColumnAutoWidth(propertyInformationSheet, 0, 3);
+ }
+
+
+ /**
+ * Adds an input prompt box to cells within the selected range. If there is already a validation
+ * for exactly those cells, the prompt box of the existing validation is overwritten.
+ *
+ * @param sheet the sheet in which the cells are
+ * @param startColIdx the index of the first column
+ * @param startRowIdx the index of the first row
+ * @param stopColIdx the index of the last column
+ * @param stopRowIdx the index of the last row
+ * @param title the title of the message in the prompt box
+ * @param content the content of the prompt box
+ */
+ public static void addInputHelper(Sheet sheet, int startColIdx, int startRowIdx,
+ int stopColIdx, int stopRowIdx, String title, String content) {
+ CellRangeAddressList validatedCells = new CellRangeAddressList(startRowIdx,
+ stopRowIdx,
+ startColIdx,
+ stopColIdx);
+
+ var validation = getValidationsExactlyCovering(sheet, validatedCells.getCellRangeAddresses()[0])
+ .stream()
+ .findFirst() // the first is applied first
+ .orElse(createFakeValidation(sheet, validatedCells));
+
+ validation.setShowPromptBox(true);
+ validation.createPromptBox(title, content);
+ sheet.addValidationData(validation);
+ }
+
+ private static DataValidation createFakeValidation(Sheet sheet,
+ CellRangeAddressList validatedCells) {
+ DataValidationHelper dataValidationHelper = sheet.getDataValidationHelper();
+ DataValidationConstraint alwaysTrue = dataValidationHelper.createCustomConstraint("TRUE");
+ return dataValidationHelper.createValidation(alwaysTrue,
+ validatedCells);
+ }
+
+ private static List getValidationsExactlyCovering(Sheet sheet,
+ CellRangeAddress cellRangeAddress) {
+ List validations = new ArrayList<>();
+ for (DataValidation dataValidation : sheet.getDataValidations()) {
+ for (CellRangeAddress rangeAddress : dataValidation.getRegions().getCellRangeAddresses()) {
+ if (rangeAddress.equals(cellRangeAddress)) {
+ validations.add(dataValidation);
+ }
+ }
+ }
+ return validations;
+ }
+
+ private static boolean hasAnyDataValidation(Sheet sheet, int startRowIdx, int startColIdx,
+ int stopRowIdx, int stopColIdx) {
+ for (DataValidation dataValidation : sheet.getDataValidations()) {
+ CellRangeAddressList regions = dataValidation.getRegions();
+ for (int i = 0; i < regions.getCellRangeAddresses().length; i++) {
+ CellRangeAddress cellRangeAddress = regions.getCellRangeAddress(i);
+ if (cellRangeAddress.intersects(
+ new CellRangeAddress(startRowIdx, stopRowIdx, startColIdx, stopColIdx))) {
+ return true;
+ }
+ }
+ }
+ return false;
+
+ }
+
+
public static void hideSheet(Workbook workbook, Sheet sheet) {
workbook.setSheetVisibility(workbook.getSheetIndex(sheet), SheetVisibility.VERY_HIDDEN);
diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementEditTemplate.java b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementEditTemplate.java
index b75de213e..256f16104 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementEditTemplate.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementEditTemplate.java
@@ -1,6 +1,8 @@
package life.qbic.datamanager.templates.measurement;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createBoldCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createDefaultCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createLinkHeaderCellStyle;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createOptionArea;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createReadOnlyCellStyle;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createReadOnlyHeaderCellStyle;
@@ -13,18 +15,23 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import life.qbic.application.commons.ApplicationException;
import life.qbic.application.commons.ApplicationException.ErrorCode;
import life.qbic.datamanager.download.DownloadContentProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
import life.qbic.datamanager.parser.measurement.NGSMeasurementEditColumn;
import life.qbic.datamanager.templates.XLSXTemplateHelper;
import life.qbic.datamanager.views.projects.project.measurements.NGSMeasurementEntry;
import life.qbic.logging.api.Logger;
import life.qbic.projectmanagement.application.measurement.NGSMeasurementMetadata;
import life.qbic.projectmanagement.domain.model.measurement.NGSMeasurement;
+import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
@@ -47,20 +54,6 @@ public class NGSMeasurementEditTemplate implements DownloadContentProvider {
private String fileNamePrefix = DEFAULT_FILE_NAME_PREFIX;
private static final int DEFAULT_GENERATED_ROW_COUNT = 200;
- private enum SequencingReadType {
- SINGLE_END("single-end"),
- PAIRED_END("paired-end");
- private final String presentationString;
-
- SequencingReadType(String presentationString) {
- this.presentationString = presentationString;
- }
-
- static List getOptions() {
- return Arrays.stream(values()).map(it -> it.presentationString).toList();
- }
- }
-
private static void setAutoWidth(Sheet sheet) {
for (int col = 0; col <= NGSMeasurementEditColumn.values().length; col++) {
sheet.autoSizeColumn(col);
@@ -68,15 +61,14 @@ private static void setAutoWidth(Sheet sheet) {
}
private static void writeMeasurementIntoRow(NGSMeasurementEntry ngsMeasurementEntry,
- Row entryRow, CellStyle readOnlyCellStyle) {
-
+ Row entryRow, CellStyle defaultStyle, CellStyle readOnlyCellStyle) {
for (NGSMeasurementEditColumn measurementColumn : NGSMeasurementEditColumn.values()) {
var value = switch (measurementColumn) {
case MEASUREMENT_ID -> ngsMeasurementEntry.measurementCode();
case SAMPLE_ID -> ngsMeasurementEntry.sampleInformation().sampleId();
case SAMPLE_NAME -> ngsMeasurementEntry.sampleInformation().sampleName();
case POOL_GROUP -> ngsMeasurementEntry.samplePoolGroup();
- case ORGANISATION_ID -> ngsMeasurementEntry.organisationId();
+ case ORGANISATION_URL -> ngsMeasurementEntry.organisationId();
case ORGANISATION_NAME -> ngsMeasurementEntry.organisationName();
case FACILITY -> ngsMeasurementEntry.facility();
case INSTRUMENT -> ngsMeasurementEntry.instrumentCURI();
@@ -91,12 +83,13 @@ private static void writeMeasurementIntoRow(NGSMeasurementEntry ngsMeasurementEn
};
var cell = getOrCreateCell(entryRow, measurementColumn.columnIndex());
cell.setCellValue(value);
+ cell.setCellStyle(defaultStyle);
if (measurementColumn.isReadOnly()) {
cell.setCellStyle(readOnlyCellStyle);
}
}
-
-
+
+
}
public void setMeasurements(List measurements, String fileNamePrefix) {
@@ -117,28 +110,67 @@ public byte[] getContent() {
CellStyle readOnlyCellStyle = createReadOnlyCellStyle(workbook);
CellStyle readOnlyHeaderStyle = createReadOnlyHeaderCellStyle(workbook);
CellStyle boldStyle = createBoldCellStyle(workbook);
+ CellStyle linkHeaderStyle = createLinkHeaderCellStyle(workbook);
+ CellStyle defaultStyle = createDefaultCellStyle(workbook);
Sheet sheet = workbook.createSheet("NGS Measurement Metadata");
Row header = getOrCreateRow(sheet, 0);
- for (NGSMeasurementEditColumn value : NGSMeasurementEditColumn.values()) {
- var cell = getOrCreateCell(header, value.columnIndex());
- if (value.isMandatory()) {
- cell.setCellValue(value.headerName() + "*");
+ for (NGSMeasurementEditColumn column : NGSMeasurementEditColumn.values()) {
+ var cell = getOrCreateCell(header, column.columnIndex());
+ if (column.isMandatory()) {
+ cell.setCellValue(column.headerName() + "*");
} else {
- cell.setCellValue(value.headerName());
+ cell.setCellValue(column.headerName());
}
cell.setCellStyle(boldStyle);
- if (value.isReadOnly()) {
+ if (column.isReadOnly()) {
cell.setCellStyle(readOnlyHeaderStyle);
+ } else if (column.equals(NGSMeasurementEditColumn.ORGANISATION_URL)) {
+ CreationHelper creationHelper = workbook.getCreationHelper();
+ Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL);
+ hyperlink.setAddress("https://ror.org");
+ cell.setCellStyle(linkHeaderStyle);
+ cell.setHyperlink(hyperlink);
+ } else if (column.equals(NGSMeasurementEditColumn.INSTRUMENT)) {
+ CreationHelper creationHelper = workbook.getCreationHelper();
+ Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL);
+ hyperlink.setAddress("https://rdm.qbic.uni-tuebingen.de");
+ cell.setCellStyle(linkHeaderStyle);
+ cell.setHyperlink(hyperlink);
}
+ //add helper to header
+ column.getFillHelp().ifPresent(
+ helper -> XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ 0,
+ column.columnIndex(),
+ 0,
+ helper.exampleValue(),
+ helper.description()));
+ }
+
+ // add property information order of columns matters!!
+ for (NGSMeasurementEditColumn column : Arrays.stream(
+ NGSMeasurementEditColumn.values())
+ .sorted(Comparator.comparing(NGSMeasurementEditColumn::columnIndex)).toList()) {
+ // add property information
+ var exampleValue = column.getFillHelp().map(Helper::exampleValue).orElse("");
+ var description = column.getFillHelp().map(Helper::description).orElse("");
+ XLSXTemplateHelper.addPropertyInformation(workbook,
+ column.headerName(),
+ column.isMandatory(),
+ exampleValue,
+ description,
+ defaultStyle,
+ boldStyle);
}
var startIndex = 1; // start in row number 2 with index 1 as the header row has number 1 index 0
int rowIndex = startIndex;
for (NGSMeasurementEntry measurement : measurements) {
Row row = getOrCreateRow(sheet, rowIndex);
- writeMeasurementIntoRow(measurement, row, readOnlyCellStyle);
+ writeMeasurementIntoRow(measurement, row, defaultStyle, readOnlyCellStyle);
rowIndex++;
}
@@ -157,6 +189,18 @@ public byte[] getContent() {
DEFAULT_GENERATED_ROW_COUNT - 1,
sequencingReadTypeArea);
+ for (NGSMeasurementEditColumn column : NGSMeasurementEditColumn.values()) {
+ column.getFillHelp().ifPresent(
+ helper -> XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ startIndex,
+ column.columnIndex(),
+ DEFAULT_GENERATED_ROW_COUNT - 1,
+ helper.exampleValue(),
+ helper.description())
+ );
+ }
+
setAutoWidth(sheet);
workbook.setActiveSheet(0);
diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementRegisterTemplate.java b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementRegisterTemplate.java
new file mode 100644
index 000000000..edc91ddb9
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/NGSMeasurementRegisterTemplate.java
@@ -0,0 +1,171 @@
+package life.qbic.datamanager.templates.measurement;
+
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createBoldCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createDefaultCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createLinkHeaderCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createOptionArea;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createReadOnlyHeaderCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.getOrCreateCell;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.getOrCreateRow;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.hideSheet;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.lockSheet;
+import static life.qbic.logging.service.LoggerFactory.logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Comparator;
+import life.qbic.application.commons.ApplicationException;
+import life.qbic.application.commons.ApplicationException.ErrorCode;
+import life.qbic.datamanager.download.DownloadContentProvider;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
+import life.qbic.datamanager.parser.measurement.NGSMeasurementRegisterColumn;
+import life.qbic.datamanager.templates.Template;
+import life.qbic.datamanager.templates.XLSXTemplateHelper;
+import life.qbic.logging.api.Logger;
+import life.qbic.projectmanagement.application.measurement.NGSMeasurementMetadata;
+import life.qbic.projectmanagement.domain.model.measurement.NGSMeasurement;
+import org.apache.poi.common.usermodel.HyperlinkType;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.Hyperlink;
+import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * NGS Measurement Content Provider
+ *
+ * Implementation of the {@link DownloadContentProvider} providing the content and file name for any
+ * files created from {@link NGSMeasurement} and {@link NGSMeasurementMetadata}
+ *
+ * Implementation of the {@link DownloadContentProvider} providing the content and file name for any files created
+ * from {@link ProteomicsMeasurement}
+ * and {@link ProteomicsMeasurementMetadata}
+ *
The Excel spreadsheet containing the required information for mass spectrometry measurement
- * registration-
- *
- * @since 1.0.0
- */
-public class ProteomicsMeasurementTemplate extends Template {
-
- private static final String MS_MEASUREMENT_TEMPLATE_PATH = "templates/proteomics_measurement_registration_sheet.xlsx";
-
- private static final String MS_MEASUREMENT_TEMPLATE_FILENAME = "proteomics_measurement_registration_sheet.xlsx";
-
- private static final String MS_MEASUREMENT_TEMPLATE_DOMAIN_NAME = "Proteomics Template";
-
- public ProteomicsMeasurementTemplate() {
- }
-
- @Override
- public byte[] getContent() {
- try {
- return Objects.requireNonNull(
- getClass().getClassLoader().getResourceAsStream(MS_MEASUREMENT_TEMPLATE_PATH))
- .readAllBytes();
- } catch (IOException e) {
- throw new RuntimeException("Cannot get content for template: " + MS_MEASUREMENT_TEMPLATE_PATH,
- e);
- }
- }
-
- @Override
- public String getFileName() {
- return MS_MEASUREMENT_TEMPLATE_FILENAME;
- }
-
- @Override
- public String getDomainName() {
- return MS_MEASUREMENT_TEMPLATE_DOMAIN_NAME;
- }
-}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/SequencingReadType.java b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/SequencingReadType.java
new file mode 100644
index 000000000..9ded7a11f
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/templates/measurement/SequencingReadType.java
@@ -0,0 +1,18 @@
+package life.qbic.datamanager.templates.measurement;
+
+import java.util.Arrays;
+import java.util.List;
+
+enum SequencingReadType {
+ SINGLE_END("single-end"),
+ PAIRED_END("paired-end");
+ private final String presentationString;
+
+ SequencingReadType(String presentationString) {
+ this.presentationString = presentationString;
+ }
+
+ static List getOptions() {
+ return Arrays.stream(values()).map(it -> it.presentationString).toList();
+ }
+}
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 2b02f1ce9..68a1815c7 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
@@ -1,15 +1,22 @@
package life.qbic.datamanager.templates.sample;
-import static life.qbic.datamanager.templates.XLSXTemplateHelper.*;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.addDataValidation;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createBoldCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createDefaultCellStyle;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createOptionArea;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createReadOnlyHeaderCellStyle;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.getOrCreateCell;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.getOrCreateRow;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.hideSheet;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.lockSheet;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.setColumnAutoWidth;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.setColumnWidth;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
+import life.qbic.datamanager.parser.ExampleProvider.Helper;
import life.qbic.datamanager.parser.sample.RegisterColumn;
import life.qbic.datamanager.templates.XLSXTemplateHelper;
import org.apache.poi.ss.usermodel.Name;
@@ -66,6 +73,7 @@ public static XSSFWorkbook createRegistrationTemplate(List conditions,
XSSFWorkbook workbook = new XSSFWorkbook();
var readOnlyHeaderStyle = createReadOnlyHeaderCellStyle(workbook);
var boldCellStyle = createBoldCellStyle(workbook);
+ var defaultStyle = createDefaultCellStyle(workbook);
var sheet = workbook.createSheet("Sample Metadata");
@@ -82,7 +90,34 @@ public static XSSFWorkbook createRegistrationTemplate(List conditions,
if (column.isReadOnly()) {
cell.setCellStyle(readOnlyHeaderStyle);
}
+
+ //add helper to header
+ column.getFillHelp().ifPresent(
+ helper -> XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ 0,
+ column.columnIndex(),
+ 0,
+ helper.exampleValue(),
+ helper.description()));
+ }
+
+ // add property information order of columns matters!!
+ for (RegisterColumn column : Arrays.stream(
+ RegisterColumn.values())
+ .sorted(Comparator.comparing(RegisterColumn::columnIndex)).toList()) {
+ // add property information
+ var exampleValue = column.getFillHelp().map(Helper::exampleValue).orElse("");
+ var description = column.getFillHelp().map(Helper::description).orElse("");
+ XLSXTemplateHelper.addPropertyInformation(workbook,
+ column.headerName(),
+ column.isMandatory(),
+ exampleValue,
+ description,
+ defaultStyle,
+ boldCellStyle);
}
+
var startIndex = 1; //start in the second row with index 1.
var hiddenSheet = workbook.createSheet("hidden");
@@ -124,6 +159,18 @@ public static XSSFWorkbook createRegistrationTemplate(List conditions,
MAX_ROW_INDEX_TO,
specimenOptions);
+ for (var column : RegisterColumn.values()) {
+ column.getFillHelp().ifPresent(
+ helper -> XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ startIndex,
+ column.columnIndex(),
+ MAX_ROW_INDEX_TO,
+ helper.exampleValue(),
+ helper.description())
+ );
+ }
+
setColumnAutoWidth(sheet, 0, RegisterColumn.maxColumnIndex());
// Auto width ignores cell validation values (e.g. a list of valid entries). So we need
// to set them explicit
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 0fb98dc01..c865454e7 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
@@ -1,6 +1,7 @@
package life.qbic.datamanager.templates.sample;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.addDataValidation;
+import static life.qbic.datamanager.templates.XLSXTemplateHelper.createDefaultCellStyle;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createOptionArea;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.createReadOnlyCellStyle;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.getOrCreateCell;
@@ -9,7 +10,10 @@
import static life.qbic.datamanager.templates.XLSXTemplateHelper.lockSheet;
import static life.qbic.datamanager.templates.XLSXTemplateHelper.setColumnAutoWidth;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
+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.sample.PropertyConversion;
@@ -57,6 +61,7 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List samples, List XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ 0,
+ column.columnIndex(),
+ 0,
+ helper.exampleValue(),
+ helper.description()));
}
+
+ // add property information order of columns matters!!
+ for (EditColumn column : Arrays.stream(
+ EditColumn.values())
+ .sorted(Comparator.comparing(EditColumn::columnIndex)).toList()) {
+ // add property information
+ var exampleValue = column.getFillHelp().map(Helper::exampleValue).orElse("");
+ var description = column.getFillHelp().map(Helper::description).orElse("");
+ XLSXTemplateHelper.addPropertyInformation(workbook,
+ column.headerName(),
+ column.isMandatory(),
+ exampleValue,
+ description,
+ defaultStyle,
+ boldCellStyle);
+ }
+
var startIndex = 1; //start in the second row with index 1.
int rowIndex = startIndex;
for (Sample sample : samples) {
Row row = getOrCreateRow(sheet, rowIndex);
var experimentalGroup = experimentalGroups.stream()
.filter(group -> group.id() == sample.experimentalGroupId()).findFirst().orElseThrow();
- fillRowWithSampleMetadata(row, sample, experimentalGroup.condition(), readOnlyCellStyle);
+ fillRowWithSampleMetadata(row, sample, experimentalGroup.condition(), defaultStyle,
+ readOnlyCellStyle);
rowIndex++;
}
+
var hiddenSheet = workbook.createSheet("hidden");
Name analysisToBePerformedOptions = createOptionArea(hiddenSheet, "Analysis to be performed",
analysisToPerform);
@@ -122,6 +155,18 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List XLSXTemplateHelper.addInputHelper(sheet,
+ column.columnIndex(),
+ startIndex,
+ column.columnIndex(),
+ MAX_ROW_INDEX_TO,
+ helper.exampleValue(),
+ helper.description())
+ );
+ }
+
setColumnAutoWidth(sheet, 0, EditColumn.maxColumnIndex());
workbook.setActiveSheet(0);
lockSheet(hiddenSheet);
@@ -131,7 +176,7 @@ public static XSSFWorkbook createUpdateTemplate(List samples, List sample.sampleCode().code();
@@ -146,6 +191,7 @@ private static void fillRowWithSampleMetadata(Row row, Sample sample,
};
var cell = getOrCreateCell(row, column.columnIndex());
cell.setCellValue(value);
+ cell.setCellStyle(defaultStyle);
if (column.isReadOnly()) {
cell.setCellStyle(readOnlyCellStyle);
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/MeasurementType.java b/user-interface/src/main/java/life/qbic/datamanager/views/MeasurementType.java
new file mode 100644
index 000000000..8fc5b1306
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/MeasurementType.java
@@ -0,0 +1,24 @@
+package life.qbic.datamanager.views;
+
+/**
+ * Measurement Type
+ *
+ * Some controlled enum vocabulary for different measurement types to use in the frontend part of
+ * the application.
+ *
+ * @since 1.6.0
+ */
+public enum MeasurementType {
+ PROTEOMICS("Proteomics"),
+ GENOMICS("Genomics");
+
+ private final String type;
+
+ MeasurementType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/TagFactory.java b/user-interface/src/main/java/life/qbic/datamanager/views/TagFactory.java
new file mode 100644
index 000000000..0fc3195c8
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/TagFactory.java
@@ -0,0 +1,42 @@
+package life.qbic.datamanager.views;
+
+import life.qbic.datamanager.views.general.Tag;
+import life.qbic.datamanager.views.general.Tag.TagColor;
+
+/**
+ * Tag Factory
+ *
+ *
Create display tags for all thinkable use cases.
+ *
+ * @since 1.6.0
+ */
+public class TagFactory {
+
+ private TagFactory() {}
+
+ public static Tag forMeasurement(MeasurementType measurementType) {
+ return switch (measurementType) {
+ case GENOMICS -> pinkTag("Genomics");
+ case PROTEOMICS -> violetTag("Proteomics");
+ };
+ }
+
+ public static Tag forCustom(String label, TagColor tagColor) {
+ return tagWithColor(label, tagColor);
+ }
+
+ private static Tag tagWithColor(String label, TagColor color) {
+ var tag = new Tag(label);
+ tag.setTagColor(color);
+ return tag;
+ }
+
+ private static Tag violetTag(String label) {
+ return tagWithColor(label, TagColor.VIOLET);
+ }
+
+ private static Tag pinkTag(String label) {
+ return tagWithColor(label, TagColor.PINK);
+ }
+
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/events/ContactUpdateEvent.java b/user-interface/src/main/java/life/qbic/datamanager/views/events/ContactUpdateEvent.java
new file mode 100644
index 000000000..ed4e4afc8
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/events/ContactUpdateEvent.java
@@ -0,0 +1,30 @@
+package life.qbic.datamanager.views.events;
+
+import com.vaadin.flow.component.ComponentEvent;
+import java.util.Objects;
+import java.util.Optional;
+import life.qbic.datamanager.views.projects.ProjectInformation;
+import life.qbic.datamanager.views.projects.edit.EditContactDialog;
+
+
+public class ContactUpdateEvent extends ComponentEvent {
+
+ private final ProjectInformation projectInfo;
+
+ /**
+ * Creates a new event using the given source and indicator whether the event originated from the
+ * client side or the server side.
+ *
+ * @param source the source component
+ * @param fromClient true if the event originated from the client
+ * side, false otherwise
+ */
+ public ContactUpdateEvent(EditContactDialog source, boolean fromClient, ProjectInformation projectInformation) {
+ super(source, fromClient);
+ this.projectInfo = Objects.requireNonNull(projectInformation);
+ }
+
+ public Optional content() {
+ return Optional.ofNullable(projectInfo);
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/events/FundingInformationUpdateEvent.java b/user-interface/src/main/java/life/qbic/datamanager/views/events/FundingInformationUpdateEvent.java
new file mode 100644
index 000000000..a828718f4
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/events/FundingInformationUpdateEvent.java
@@ -0,0 +1,20 @@
+package life.qbic.datamanager.views.events;
+
+import com.vaadin.flow.component.ComponentEvent;
+import java.util.Optional;
+import life.qbic.datamanager.views.projects.ProjectInformation;
+import life.qbic.datamanager.views.projects.edit.EditFundingInformationDialog;
+
+public class FundingInformationUpdateEvent extends ComponentEvent {
+
+ private final ProjectInformation projectInformation;
+
+ public FundingInformationUpdateEvent(EditFundingInformationDialog source, boolean fromClient, ProjectInformation projectInformation) {
+ super(source, fromClient);
+ this.projectInformation = projectInformation;
+ }
+
+ public Optional content() {
+ return Optional.ofNullable(projectInformation);
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/events/ProjectDesignUpdateEvent.java b/user-interface/src/main/java/life/qbic/datamanager/views/events/ProjectDesignUpdateEvent.java
new file mode 100644
index 000000000..01b11ef9a
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/events/ProjectDesignUpdateEvent.java
@@ -0,0 +1,21 @@
+package life.qbic.datamanager.views.events;
+
+import com.vaadin.flow.component.ComponentEvent;
+import java.util.Optional;
+import life.qbic.datamanager.views.projects.ProjectInformation;
+import life.qbic.datamanager.views.projects.edit.EditProjectDesignDialog;
+
+
+public class ProjectDesignUpdateEvent extends ComponentEvent {
+
+ private final ProjectInformation projectInformation;
+
+ public ProjectDesignUpdateEvent(EditProjectDesignDialog source, boolean fromClient, ProjectInformation projectInformation) {
+ super(source, fromClient);
+ this.projectInformation = projectInformation;
+ }
+
+ public Optional content() {
+ return Optional.ofNullable(projectInformation);
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/DetailBox.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/DetailBox.java
new file mode 100644
index 000000000..cbba54bae
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/DetailBox.java
@@ -0,0 +1,119 @@
+package life.qbic.datamanager.views.general;
+
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.icon.Icon;
+
+/**
+ * Detail Box
+ *
+ * A data manager detail box contains of the two main layout sections:
+ *
+ *
+ *
Header Section
+ *
Content Section
+ *
+ *
+ * Detail boxes are used to visually highlight some contextual information to the user,
+ * with a descriptive heading, icons and some border to separate it from the surrounding elements.
+ *
+ * Developer hint: the content section can be filled with any content, but the height is currently
+ * restricted to 10rem (css: detail-box). Then the overflow will trigger a scrollbar in the content section.
+ *
+ * @since 1.6.0
+ */
+public class DetailBox extends Div {
+
+ private Div headerSection;
+
+ private Div contentSection;
+
+ private Header header;
+
+ private Component content;
+
+ public DetailBox() {
+ addClassName("detail-box");
+ headerSection = new Div();
+ headerSection.addClassName("detail-box-child");
+ contentSection = new Div();
+ contentSection.addClassName("detail-box-child");
+ contentSection.addClassName("overflow-scroll-height");
+ add(headerSection, contentSection);
+ }
+
+ public void setHeader(Header header) {
+ this.header = header;
+ rebuild();
+ }
+
+ public void setContent(Component content) {
+ this.content = content;
+ rebuild();
+ }
+
+ private void rebuild() {
+ headerSection.removeAll();
+ contentSection.removeAll();
+ if (header != null) {
+ headerSection.add(header);
+ }
+ if (content != null) {
+ add(content);
+ contentSection.add(content);
+ }
+ }
+
+
+ public static class Header extends Div {
+
+ private boolean iconVisible = false;
+
+ private Icon icon;
+
+ private Div heading;
+
+ public Header() {
+ addClassName("detail-box-header");
+ heading = new Div();
+ }
+
+ public Header(Icon icon, String text) {
+ this();
+ this.icon = icon;
+ heading.setText(text);
+ showIcon();
+ rebuild();
+ }
+
+ public Header(String text) {
+ this();
+ heading.setText(text);
+ rebuild();
+ }
+
+ private void setIconVisibility(boolean visible) {
+ iconVisible = visible;
+ }
+
+ public void showIcon() {
+ setIconVisibility(true);
+ rebuild();
+ }
+
+ public void hideIcon() {
+ setIconVisibility(false);
+ rebuild();
+ }
+
+ private void rebuild() {
+ removeAll();
+ if (iconVisible && icon != null) {
+ add(icon);
+ }
+ if (heading != null) {
+ add(heading);
+ }
+ }
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/HasBoundField.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/HasBoundField.java
new file mode 100644
index 000000000..0d0132833
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/HasBoundField.java
@@ -0,0 +1,51 @@
+package life.qbic.datamanager.views.general;
+
+import com.vaadin.flow.data.binder.ValidationException;
+
+/**
+ * Bound Field
+ *
+ *
A bound field offers some common access and behaviour to the implemented bound field.
+ *
+ * @since 1.6.0
+ */
+public interface HasBoundField {
+
+ /**
+ * Returns the field with bindings
+ *
+ * @since 1.6.0
+ */
+ T getField();
+
+ /**
+ * Returns the bound value
+ *
+ * @throws ValidationException if any validation of the field fails
+ * @since 1.6.0
+ */
+ V getValue() throws ValidationException;
+
+ /**
+ * Set the bound value for the field. This will also update the field content.
+ *
+ * @param value sets an original value
+ * @since 1.6.0
+ */
+ void setValue(V value);
+
+ /**
+ * true, if the bound value is valid, else returns false
+ *
+ * @since 1.6.0
+ */
+ boolean isValid();
+
+ /**
+ * Indicates, if the original value has changed after being set via {@link #setValue(Object)}
+ *
+ * @return true, if the original value has changed, else false
+ * @since 1.6.0
+ */
+ boolean hasChanged();
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/Heading.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/Heading.java
new file mode 100644
index 000000000..13354bfcb
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/Heading.java
@@ -0,0 +1,66 @@
+package life.qbic.datamanager.views.general;
+
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+
+/**
+ * Heading (with icon support)
+ *
+ * @since 1.6.0
+ */
+public class Heading extends Div {
+
+ private Icon icon;
+
+ private Span text;
+
+ private Heading() {
+ addClassName("heading-with-icon");
+ this.icon = VaadinIcon.VAADIN_H.create();
+ this.text = new Span();
+ rebuild();
+ }
+
+ public static Heading createEmpty() {
+ return new Heading();
+ }
+
+ public static Heading withIcon(Icon icon) {
+ var headerWithIcon = new Heading();
+ headerWithIcon.setIcon(icon);
+ return headerWithIcon;
+ }
+
+ public static Heading withText(String text) {
+ var headerWithText = new Heading();
+ headerWithText.setText(text);
+ return headerWithText;
+ }
+
+ public static Heading withIconAndText(Icon icon, String text) {
+ var headerWithIconAndText = new Heading();
+ headerWithIconAndText.setIcon(icon);
+ headerWithIconAndText.setCustomText(text);
+ return headerWithIconAndText;
+ }
+
+ private void setCustomText(String text) {
+ this.text = new Span(text);
+ rebuild();
+ }
+
+ public void setIcon(Icon icon) {
+ this.icon = icon;
+ rebuild();
+ }
+
+ private void rebuild() {
+ removeAll();
+ add(icon);
+ add(text);
+ }
+
+
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/IconLabel.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/IconLabel.java
new file mode 100644
index 000000000..a53f34d1e
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/IconLabel.java
@@ -0,0 +1,75 @@
+package life.qbic.datamanager.views.general;
+
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+
+/**
+ * Icon label
+ *
+ *
+ * Renders an ontology term nicely as a badge with href.
+ *
+ * @since 1.6.0
+ */
+public class OntologyTermDisplay extends Div {
+
+
+ public OntologyTermDisplay(String label, String curie, String reference) {
+ Div ontology = new Div();
+ ontology.addClassName("vertical-list");
+ Span ontologyLabel = new Span(label);
+ ontologyLabel.addClassName("overflow-hidden-ellipsis");
+ Span ontologyLink = new Span(OboIdFormatter.render(curie));
+ ontologyLink.addClassName("ontology-link");
+ Anchor ontologyClassIri = new Anchor(reference, ontologyLink);
+ ontologyClassIri.setTarget(AnchorTarget.BLANK);
+ ontology.add(ontologyLabel, ontologyClassIri);
+ ontology.addClassNames("ontology-term", "gap-small");
+ add(ontology);
+ }
+
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/AutocompleteContactField.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/AutocompleteContactField.java
index 0fd51a582..39aeb0f74 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/AutocompleteContactField.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/AutocompleteContactField.java
@@ -14,10 +14,7 @@
import java.util.ArrayList;
import java.util.List;
import life.qbic.datamanager.views.general.HasBinderValidation;
-import life.qbic.projectmanagement.application.authorization.QbicOidcUser;
-import life.qbic.projectmanagement.application.authorization.QbicUserDetails;
-import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
-import org.springframework.security.core.context.SecurityContextHolder;
+import life.qbic.datamanager.views.general.utils.Utility;
/**
* A component for contact person input
@@ -27,6 +24,7 @@
*
* @since 1.0.0
*/
+@Deprecated(since = "1.6.0")
public class AutocompleteContactField extends CustomField implements
HasBinderValidation {
@@ -101,20 +99,8 @@ public AutocompleteContactField(String label, String shortName) {
private void onSelfSelected(
ComponentValueChangeEvent checkboxvalueChangeEvent) {
if (Boolean.TRUE.equals(checkboxvalueChangeEvent.getValue())) {
- var principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- String fullName;
- String emailAddress;
- if (principal instanceof QbicUserDetails qbicUserDetails) {
- fullName = qbicUserDetails.fullName();
- emailAddress = qbicUserDetails.getEmailAddress();
- } else if (principal instanceof QbicOidcUser qbicOidcUser) {
- fullName = qbicOidcUser.getFullName();
- emailAddress = qbicOidcUser.getEmail();
- } else {
- throw new AuthenticationCredentialsNotFoundException("Unknown authentication principal");
- }
- Contact userAsContact = new Contact(fullName, emailAddress);
- setContact(userAsContact);
+ var userAsContact = Utility.tryToLoadFromPrincipal();
+ userAsContact.ifPresent(this::setContact);
}
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/BoundContactField.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/BoundContactField.java
new file mode 100644
index 000000000..b1cae8af6
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/contact/BoundContactField.java
@@ -0,0 +1,186 @@
+package life.qbic.datamanager.views.general.contact;
+
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.data.binder.Binder;
+import com.vaadin.flow.data.binder.ValidationException;
+import com.vaadin.flow.data.validator.EmailValidator;
+import com.vaadin.flow.function.SerializablePredicate;
+import java.util.Objects;
+import life.qbic.datamanager.views.general.HasBoundField;
+
+/**
+ * Bound Contact Field
+ *
+ * Binds a {@link ContactField} to a {@link Contact}-
+ *
+ * @since 1.6.0
+ */
+public class BoundContactField implements HasBoundField {
+
+ private final ContactField contactField;
+
+ private final Binder binder;
+
+ private Contact originalValue;
+
+ private BoundContactField(ContactField contactField,
+ SerializablePredicate predicate) {
+ this.contactField = contactField;
+ this.binder = createBinder(predicate, contactField);
+ binder.addStatusChangeListener(
+ event -> updateStatus(contactField, event.hasValidationErrors()));
+ this.originalValue = new Contact("", "");
+ }
+
+ private static void updateStatus(ContactField contactField, boolean isInvalid) {
+ contactField.getElement().setProperty("invalid", isInvalid);
+ updateStatus(contactField.getEmailTextField(), isInvalid);
+ updateStatus(contactField.getFullNameTextField(), isInvalid);
+ }
+
+ private static void updateStatus(TextField textField, boolean isInvalid) {
+ textField.setInvalid(isInvalid);
+ }
+
+ /**
+ * The contact field will only invalidate, if one of the fields is empty. Since it is optional,
+ * the contact field will not invalidate, if both inputs are empty.
+ *
+ * @param contactField
+ * @return
+ * @since
+ */
+ public static BoundContactField createOptional(ContactField contactField) {
+ contactField.setOptional(true);
+ return new BoundContactField(contactField, isOptional());
+ }
+
+ /**
+ * The contact field will invalidate, if one of the fields is empty or both are empty, since it is
+ * mandatory to be filled with information.
+ *
+ * @param contactField
+ * @return
+ * @since
+ */
+ public static BoundContactField createMandatory(ContactField contactField) {
+ return new BoundContactField(contactField, isMandatory());
+ }
+
+ /**
+ * This predicate will return true, if all fields are filled.
+ *
+ * If either is filled alone or none, the predicate will return false
+ *
+ * @return
+ * @since
+ */
+ private static SerializablePredicate isMandatory() {
+ return contact -> {
+ var onlyEmailEmpty = contact.getEmail().isBlank() && !contact.getFullName().isBlank();
+ var onlyNameEmpty = !contact.getEmail().isBlank() && contact.getFullName().isBlank();
+ var bothEmpty = contact.getEmail().isBlank() && contact.getFullName().isBlank();
+ return !(onlyEmailEmpty || onlyNameEmpty || bothEmpty);
+ };
+ }
+
+ /**
+ * This predicate will return true, if all fields are empty or both are filled.
+ *
+ * Binds a {@link FundingField} to a {@link FundingEntry}-
+ *
+ * @since 1.6.0
+ */
+public class BoundFundingField implements HasBoundField {
+
+ private final FundingField fundingField;
+
+ private final Binder binder;
+
+ private FundingEntry initValue;
+
+ public BoundFundingField(FundingField fundingField) {
+ this.fundingField = Objects.requireNonNull(fundingField);
+ this.binder = new Binder<>(FundingInformationContainer.class);
+ bindSimple(binder, fundingField);
+ }
+
+ @SafeVarargs
+ public BoundFundingField(FundingField fundingField, Validator... validators) {
+ this.fundingField = Objects.requireNonNull(fundingField);
+ this.binder = new Binder<>(FundingInformationContainer.class);
+ bindWithValidators(binder, fundingField, validators);
+ }
+
+ private static void bindSimple(Binder binder,
+ FundingField fundingField) {
+ binder.forField(fundingField)
+ .bind(FundingInformationContainer::get, FundingInformationContainer::set);
+ }
+
+ @SafeVarargs
+ private static void bindWithValidators(Binder binder,
+ FundingField fundingField, Validator... validators) {
+ var binding = binder.forField(fundingField);
+ for (Validator validator : validators) {
+ binding.withValidator(validator);
+ }
+ binding.bind(FundingInformationContainer::get, FundingInformationContainer::set);
+ }
+
+ @Override
+ public FundingField getField() {
+ return fundingField;
+ }
+
+ @Override
+ public FundingEntry getValue() throws ValidationException {
+ var container = new FundingInformationContainer();
+ binder.writeBean(container);
+ return container.get();
+ }
+
+ @Override
+ public void setValue(FundingEntry value) {
+ initValue = value;
+ var container = new FundingInformationContainer();
+ container.set(value);
+ binder.setBean(container);
+ }
+
+ @Override
+ public boolean isValid() {
+ return binder.isValid();
+ }
+
+ @Override
+ public boolean hasChanged() {
+ return binder.hasChanges() || hasChanged(initValue, binder.getBean().get());
+ }
+
+ private boolean hasChanged(FundingEntry oldValue, FundingEntry newValue) {
+ return !oldValue.equals(newValue);
+ }
+
+ public static final class FundingInformationContainer {
+
+ private FundingEntry fundingEntry;
+
+ public FundingEntry get() {
+ return fundingEntry;
+ }
+
+ public void set(FundingEntry fundingEntry) {
+ this.fundingEntry = fundingEntry;
+ }
+
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingField.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingField.java
index e70df236b..c8372c787 100644
--- a/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingField.java
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingField.java
@@ -20,11 +20,11 @@ public class FundingField extends CustomField implements HasClient
private static final long serialVersionUID = 839203706554301417L;
private final TextField label;
private final TextField referenceId;
+ private final Div layoutFundingInput;
public FundingField(String fieldLabel) {
super();
addClassName("funding-field");
- setLabel(fieldLabel);
this.label = new TextField("Grant", "e.g. SFB");
this.label.addClassName("grant-label-field");
this.referenceId = new TextField("Grant ID", "e.g. SFB 1101");
@@ -32,9 +32,21 @@ public FundingField(String fieldLabel) {
// we need to override the text-fields internal default validation, since we do not directly add binders
// with validators to the encapsulated fields, which results in removal of the invalid HTML property and disabling
// us correctly display invalid element status
+ setLabel(fieldLabel);
label.addValidationStatusChangeListener(e -> validate());
referenceId.addValidationStatusChangeListener(e -> validate());
- layoutComponent();
+ layoutFundingInput = layoutFundingInput(label, referenceId);
+ add(layoutFundingInput);
+ }
+
+ public static FundingField createVertical(String fieldLabel) {
+ return new FundingField(fieldLabel);
+ }
+
+ public static FundingField createHorizontal(String fieldLabel) {
+ var field = new FundingField(fieldLabel);
+ field.layoutFundingInput.addClassNames("flex-horizontal", "gap-m");
+ return field;
}
protected void validate() {
@@ -79,8 +91,12 @@ public void setInvalid(boolean invalid) {
referenceId.setInvalid(invalid);
}
+ public void setLabel(String label) {
+ this.label.setValue(label);
+ }
-
-
+ public void setReferenceId(String referenceId) {
+ this.referenceId.setValue(referenceId);
+ }
}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingInputForm.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingInputForm.java
new file mode 100644
index 000000000..acd00b998
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/funding/FundingInputForm.java
@@ -0,0 +1,40 @@
+package life.qbic.datamanager.views.general.funding;
+
+import com.vaadin.flow.component.formlayout.FormLayout;
+import com.vaadin.flow.data.binder.ValidationException;
+import java.util.Objects;
+import life.qbic.datamanager.views.general.HasBoundField;
+
+/**
+ * Funding Input Form
+ *
+ * Form that can be used to request funding information about a project from a user.
+ *
+ * @since 1.6.0
+ */
+public class FundingInputForm extends FormLayout {
+
+ private transient final HasBoundField fundingField;
+
+ private FundingInputForm(HasBoundField fundingField) {
+ this.fundingField = fundingField;
+ add(fundingField.getField());
+ }
+
+ public static FundingInputForm create(HasBoundField fundingField) {
+ Objects.requireNonNull(fundingField);
+ return new FundingInputForm(fundingField);
+ }
+
+ public void setContent(FundingEntry fundingEntry) {
+ fundingField.setValue(fundingEntry);
+ }
+
+ public FundingEntry fromUserInput() throws ValidationException {
+ return fundingField.getValue();
+ }
+
+ public boolean hasChanges() {
+ return fundingField.hasChanged();
+ }
+}
diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/general/section/ActionBar.java b/user-interface/src/main/java/life/qbic/datamanager/views/general/section/ActionBar.java
new file mode 100644
index 000000000..386c18976
--- /dev/null
+++ b/user-interface/src/main/java/life/qbic/datamanager/views/general/section/ActionBar.java
@@ -0,0 +1,106 @@
+package life.qbic.datamanager.views.general.section;
+
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.html.Div;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Action Bar
+ *
+ *
An actionbar offers the user the possibility to perform some action
+ * that is related to its placed context in the application.
+ *
+ * Action items can be activated or deactivated. Inactive control elements are disabled and hidden
+ * (default), active control elements are enabled and shown.
+ *
+ *
+ * Relevant CSS
+ *
+ * The relevant CSS classes for this component are:
+ *
+ *
+ *
actionbar
+ *
+ *
+ * @since 1.6.0
+ */
+public class ActionBar extends Div {
+
+ private transient ControlStrategy controlStrategy;
+
+ private List