diff --git a/pom.xml b/pom.xml
index 780a1c14..053c4d5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,8 +35,8 @@
UTF-8
17
- 3.3.1
- 1.19.8
+ 3.3.3
+ 1.20.1
more-project
@@ -84,7 +84,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.5
+ 3.3.1
org.apache.maven.plugins
@@ -408,7 +408,7 @@
co.elastic.clients
elasticsearch-java
- 8.13.4
+ 8.14.3
com.google.firebase
diff --git a/studymanager-core/pom.xml b/studymanager-core/pom.xml
index 22bbcd51..3b896f55 100644
--- a/studymanager-core/pom.xml
+++ b/studymanager-core/pom.xml
@@ -20,7 +20,7 @@
org.slf4j
slf4j-api
- 2.0.13
+ 2.0.16
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/component/Observation.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/component/Observation.java
index 2573d5d4..2f80b759 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/component/Observation.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/component/Observation.java
@@ -9,8 +9,11 @@
package io.redlink.more.studymanager.core.component;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
+import io.redlink.more.studymanager.core.ui.DataView;
+import io.redlink.more.studymanager.core.ui.DataViewInfo;
public abstract class Observation extends Component {
@@ -20,6 +23,29 @@ protected Observation(MoreObservationSDK sdk, C properties) throws Configuration
this.sdk = sdk;
}
+ /**
+ * Gets an array of DataViewInfo, which represents all possibilities to show this observation data.
+ *
+ * @return an array of DataViewInfo representing all possible views of the observation data
+ */
+ public DataViewInfo[] listViews() {
+ return new DataViewInfo[0];
+ }
+
+ /**
+ * Retrieves a specific DataView based on the given parameters.
+ *
+ * @param viewName the name of the view to retrieve
+ * @param studyGroupId the ID of the study group for filter reasons
+ * @param participantId the ID of the participant for filter reasons
+ * @param timerange the time range for the data view for filter reasons
+ * @return the requested DataView, or null if not found
+ */
+ public DataView getView(String viewName, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ return null;
+ }
+
+
@Override
public void activate() {
// no action
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/exception/ConfigurationValidationException.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/exception/ConfigurationValidationException.java
index 180cbfbc..dbea0c4c 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/exception/ConfigurationValidationException.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/exception/ConfigurationValidationException.java
@@ -9,9 +9,13 @@
package io.redlink.more.studymanager.core.exception;
import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport;
+import io.redlink.more.studymanager.core.validation.ValidationIssue;
+import java.util.List;
public class ConfigurationValidationException extends RuntimeException {
+
private final ConfigurationValidationReport report;
+
public ConfigurationValidationException(ConfigurationValidationReport report) {
this.report = report;
}
@@ -24,4 +28,20 @@ public ConfigurationValidationReport getReport() {
public String getMessage() {
return report.toString();
}
+
+ public static ConfigurationValidationException of(ConfigurationValidationReport report) {
+ return new ConfigurationValidationException(report);
+ }
+
+ public static ConfigurationValidationException of(ValidationIssue issue) {
+ return of(ConfigurationValidationReport.of(issue));
+ }
+
+ public static ConfigurationValidationException of(List issues) {
+ return of(ConfigurationValidationReport.of(issues));
+ }
+
+ public static ConfigurationValidationException ofError(String message) {
+ return of(ConfigurationValidationReport.ofError(message));
+ }
}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/io/SimpleParticipant.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/io/SimpleParticipant.java
index 08628dee..06e1c85e 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/io/SimpleParticipant.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/io/SimpleParticipant.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.core.io;
import java.time.Instant;
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/measurement/MeasurementSet.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/measurement/MeasurementSet.java
index 6e352079..ebebb96e 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/measurement/MeasurementSet.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/measurement/MeasurementSet.java
@@ -15,7 +15,7 @@ public record MeasurementSet(String id, Set values) {
public MeasurementSet {
if (id == null || values == null) {
- throw new IllegalArgumentException("Is and values must not be null");
+ throw new IllegalArgumentException("Id and values must not be null");
}
}
}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/IntegerValue.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/IntegerValue.java
index 4d55f688..31aa55b4 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/IntegerValue.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/IntegerValue.java
@@ -30,11 +30,11 @@ public Class getValueType() {
}
@Override
- public ValidationIssue validate(Integer integer) {
- if(integer != null && (integer < getMin() || integer > getMax())) {
+ public ValidationIssue doValidate(Integer integer) {
+ if (integer != null && (integer < getMin() || integer > getMax())) {
return ValidationIssue.error(this, "Value must between " + getMin() + " and " + getMax());
}
- return validationFunction != null ? validationFunction.apply(integer) : ValidationIssue.NONE;
+ return ValidationIssue.NONE;
}
public int getMin() {
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/StringValue.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/StringValue.java
index 6dbeb02b..b9981913 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/StringValue.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/StringValue.java
@@ -8,11 +8,21 @@
*/
package io.redlink.more.studymanager.core.properties.model;
+import io.redlink.more.studymanager.core.validation.ValidationIssue;
+
public class StringValue extends Value {
public StringValue(String id) {
super(id);
}
+ @Override
+ protected ValidationIssue doValidate(String s) {
+ if (isRequired() && (s == null || s.trim().isEmpty())) {
+ return ValidationIssue.requiredMissing(this);
+ }
+ return super.doValidate(s);
+ }
+
@Override
public String getType() {
return "STRING";
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/Value.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/Value.java
index a000455e..2fa6b85b 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/Value.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/properties/model/Value.java
@@ -13,35 +13,54 @@
import io.redlink.more.studymanager.core.exception.ValueNonNullException;
import io.redlink.more.studymanager.core.properties.ComponentProperties;
import io.redlink.more.studymanager.core.validation.ValidationIssue;
-
import java.util.function.Function;
public abstract class Value {
- private String id;
+ private final String id;
private String name;
private String description;
private T defaultValue;
private boolean required = false;
private boolean immutable = false;
- protected Function validationFunction = (T t) -> null;
+ private Function validationFunction = (T t) -> ValidationIssue.NONE;
public Value(String id) {
this.id = id;
}
- public ValidationIssue validate(T t) {
- return validationFunction != null ? validationFunction.apply(t) : ValidationIssue.NONE;
+ public final ValidationIssue validate(T t) {
+ if (required) {
+ if (t == null) {
+ return ValidationIssue.requiredMissing(this);
+ }
+ }
+
+ final ValidationIssue subResult = doValidate(t);
+ if (subResult != null && subResult.getType() != ValidationIssue.Type.None) {
+ return subResult;
+ }
+
+ final ValidationIssue result = validationFunction.apply(t);
+ if (result == null) {
+ return ValidationIssue.NONE;
+ } else {
+ return result;
+ }
+ }
+
+ protected ValidationIssue doValidate(T t) {
+ return ValidationIssue.NONE;
}
public T getValue(ComponentProperties properties) {
- if(properties.containsKey(id)) {
+ if (properties.containsKey(id)) {
try {
return getValueType().cast(properties.get(id));
} catch (ClassCastException e) {
throw new ValueCastException(this, getValueType());
}
} else {
- if(required && defaultValue == null) {
+ if (required && defaultValue == null) {
throw new ValueNonNullException(this);
} else {
return defaultValue;
@@ -57,6 +76,7 @@ public T getValue(ComponentProperties properties) {
public String getId() {
return id;
}
+
public String getName() {
return name;
}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/sdk/MoreObservationSDK.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/sdk/MoreObservationSDK.java
index f24fb5b6..3e1c51dc 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/sdk/MoreObservationSDK.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/sdk/MoreObservationSDK.java
@@ -8,7 +8,11 @@
*/
package io.redlink.more.studymanager.core.sdk;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
+import io.redlink.more.studymanager.core.ui.DataViewData;
+import io.redlink.more.studymanager.core.ui.DataViewRow;
+import io.redlink.more.studymanager.core.ui.ViewConfig;
import java.util.Map;
import java.util.Optional;
@@ -22,5 +26,7 @@ public interface MoreObservationSDK extends MorePlatformSDK {
void storeDataPoint(Integer participantId, String observationType, Map data);
+ DataViewData queryData(ViewConfig viewConfig, Integer studyGroupId, Integer participantId, TimeRange timerange);
+
int getObservationId();
}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataView.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataView.java
new file mode 100644
index 00000000..5f94bb0b
--- /dev/null
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataView.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.core.ui;
+
+/**
+ * Represents a data view with information, chart type, and data.
+ *
+ * @param viewInfo the information about the data view
+ * @param chartType the type of chart to be displayed
+ * @param data the data to be displayed in the view
+ */
+public record DataView(
+ DataViewInfo viewInfo,
+ ChartType chartType,
+ DataViewData data
+) {
+ /**
+ * Enumeration of possible chart types.
+ */
+ public enum ChartType {
+ LINE,
+ BAR,
+ PIE
+ }
+}
+
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewData.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewData.java
new file mode 100644
index 00000000..472ca383
--- /dev/null
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewData.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.core.ui;
+
+import java.util.List;
+
+/**
+ * Represents the data in a data view.
+ *
+ * @param labels the labels for the data
+ * @param rows the rows of data
+ */
+public record DataViewData(
+ List labels,
+ List rows
+) {
+}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewInfo.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewInfo.java
new file mode 100644
index 00000000..25bf45a0
--- /dev/null
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewInfo.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.core.ui;
+
+public interface DataViewInfo {
+ /**
+ * Gets the name of the data view.
+ * The name is the identifier of a specific view.
+ *
+ * @return the name of the data view
+ */
+ String name();
+
+ /**
+ * Gets the label of the data view.
+ * The label is a short indicator to differ between multiple views.
+ *
+ * @return the label of the data view
+ */
+ String label();
+
+ /**
+ * Gets the title of the data view.
+ * The title is a short textual information about the observation data.
+ *
+ * @return the title of the data view
+ */
+ String title();
+
+ /**
+ * Gets the description of the data view.
+ * The description explains what the given observation data shows.
+ *
+ * @return the description of the data view
+ */
+ String description();
+
+ /**
+ * Gets the chart type of the data view.
+ * The chartType indicates how the given observation data is visually shown.
+ *
+ * @return the chart type of the data view
+ */
+ DataView.ChartType chartType();
+}
\ No newline at end of file
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewRow.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewRow.java
new file mode 100644
index 00000000..f3ecf8fd
--- /dev/null
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/DataViewRow.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.core.ui;
+
+import java.util.List;
+
+/**
+ * Represents a row of data in a data view.
+ *
+ * @param label the label of the data row
+ * @param values the list of values in the data row
+ */
+public record DataViewRow(
+ String label,
+ List values
+) {
+}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/ViewConfig.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/ViewConfig.java
new file mode 100644
index 00000000..5d020711
--- /dev/null
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/ui/ViewConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.core.ui;
+
+import java.util.List;
+
+/**
+ * Represents the configuration of a data view.
+ *
+ * @param filters the list of filters applied to the data view
+ * @param rowAggregation the aggregation method for rows. In most cases, this will be the data-legend in the view/chart.
+ * @param seriesAggregation the aggregation method for series. In most cases, this will result in the x-axis of the view/chart
+ * @param operation the operation applied to the data
+ */
+public record ViewConfig(
+ List filters,
+ Aggregation rowAggregation,
+ Aggregation seriesAggregation,
+ Operation operation
+) {
+ /**
+ * Represents a filter applied to the data view.
+ */
+ public record Filter() { }
+
+ /**
+ * Enumeration of possible aggregation/grouping methods.
+ */
+ public enum Aggregation {
+ /**
+ * Group by timestamp of the observation
+ */
+ TIME,
+ /**
+ * Group by Study-Group
+ */
+ STUDY_GROUP,
+ /**
+ * Group by Participant
+ */
+ PARTICIPANT,
+ /**
+ * Group by a String-Value Field of the Observation, in most cases should be used together with
+ * the {@link Operator#COUNT COUNT}-operator
+ */
+ TERM_FIELD,
+ }
+
+ /**
+ * Represents an operation applied to the data.
+ *
+ * @param operator the operator to be used
+ * @param field the field to which the operation is applied
+ */
+ public record Operation(
+ Operator operator,
+ String field
+ ) {
+ }
+
+ /**
+ * Enumeration of possible operators.
+ */
+ public enum Operator {
+ AVG,
+ SUM,
+ MIN,
+ MAX,
+ COUNT
+ }
+}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ConfigurationValidationReport.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ConfigurationValidationReport.java
index 4d1f8feb..5c022fef 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ConfigurationValidationReport.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ConfigurationValidationReport.java
@@ -16,24 +16,27 @@ public class ConfigurationValidationReport {
private final List issues;
private ConfigurationValidationReport(List issues) {
- this.issues = issues;
+ this.issues = new ArrayList<>(issues);
}
public static ConfigurationValidationReport init() {
- return new ConfigurationValidationReport(new ArrayList<>());
+ return new ConfigurationValidationReport(List.of());
}
public static ConfigurationValidationReport of(List issues) {
- //TODO copy
return new ConfigurationValidationReport(issues);
}
public static ConfigurationValidationReport of(ValidationIssue issue) {
- return new ConfigurationValidationReport(List.of(issue));
+ return of(List.of(issue));
+ }
+
+ public static ConfigurationValidationReport ofError(String message) {
+ return init().error(message);
}
public ConfigurationValidationReport error(String message) {
- this.issues.add(ValidationIssue.error(null,message));
+ this.issues.add(ValidationIssue.error(null, message));
return this;
}
@@ -47,8 +50,9 @@ public ConfigurationValidationReport missingProperty(String property) {
}
public boolean isValid() {
- return this.listIssues().size() == 0;
+ return issues.stream().noneMatch(i -> i.getType() != ValidationIssue.Type.None);
}
+
List listIssues() {
return issues;
}
diff --git a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ValidationIssue.java b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ValidationIssue.java
index 838f19f2..23644b14 100644
--- a/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ValidationIssue.java
+++ b/studymanager-core/src/main/java/io/redlink/more/studymanager/core/validation/ValidationIssue.java
@@ -9,7 +9,6 @@
package io.redlink.more.studymanager.core.validation;
import io.redlink.more.studymanager.core.properties.model.Value;
-
import java.util.Optional;
public class ValidationIssue {
@@ -19,28 +18,38 @@ public enum Type {
WARNING
}
- public static ValidationIssue NONE = new ValidationIssue(Type.None, null, null);
+ public static ValidationIssue NONE = new ValidationIssue(Type.None, null, null, null);
- private Type type;
- private String propertyId;
- private String message;
+ private final Type type;
+ private final String propertyId;
+ private final String message;
+ private String componentTitle;
- private ValidationIssue(Type type, String propertyId, String message) {
+ private ValidationIssue(Type type, String propertyId, String message, String componentTitle) {
this.type = type;
this.propertyId = propertyId;
this.message = message;
+ this.componentTitle = componentTitle;
}
public static boolean nonNone(ValidationIssue issue) {
return issue != null && Type.None != issue.type;
}
- public static ValidationIssue error(Value value, String message) {
- return new ValidationIssue(Type.ERROR, Optional.ofNullable(value).map(Value::getId).orElse(null), message);
+ public static ValidationIssue error(Value> value, String message) {
+ return new ValidationIssue(Type.ERROR, Optional.ofNullable(value).map(Value::getId).orElse(null), message, null);
+ }
+
+ public static ValidationIssue warning(Value> value, String message) {
+ return new ValidationIssue(Type.WARNING, Optional.ofNullable(value).map(Value::getId).orElse(null), message, null);
+ }
+
+ public static ValidationIssue requiredMissing(Value> value) {
+ return error(value, "global.error.required");
}
- public static ValidationIssue warning(Value value, String message) {
- return new ValidationIssue(Type.WARNING, Optional.ofNullable(value).map(Value::getId).orElse(null), message);
+ public static ValidationIssue immutablePropertyChanged(Value> value) {
+ return error(value, "global.error.immutable");
}
public Type getType() {
@@ -54,4 +63,12 @@ public String getMessage() {
public String getPropertyId() {
return propertyId;
}
+
+ public String getComponentTitle() {
+ return componentTitle;
+ }
+
+ public void setComponentTitle(String componentTitle) {
+ this.componentTitle = componentTitle;
+ }
}
diff --git a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/DataPointQuery.java b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/DataPointQuery.java
index 7ce7b11e..8ca59eb2 100644
--- a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/DataPointQuery.java
+++ b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/DataPointQuery.java
@@ -47,7 +47,7 @@ private String getDataSelector() {
if("=".equals(operator) || "==".equals(operator)) {
return "data_" + observationProperty + ":" + getSanitizedPropertyValue();
} else if("!=".equals(operator)) {
- return "!(data_" + observationProperty + ":" + getSanitizedPropertyValue() + ")";
+ return "NOT data_" + observationProperty + ":" + getSanitizedPropertyValue();
} else if("<".equals(operator) || ">".equals(operator) || "<=".equals(operator) || ">=".equals(operator)) {
return "data_" + observationProperty + ":" + operator + propertyValue;
} else {
diff --git a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/QueryObject.java b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/QueryObject.java
index 3b39a0ec..71aac55d 100644
--- a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/QueryObject.java
+++ b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/QueryObject.java
@@ -34,7 +34,7 @@ public QueryObject() {
public Set parameter;
public String toQueryString() {
- return "(" + parameter.stream().map(DataPointQuery::toQueryString).collect(Collectors.joining(" AND ")) + ")";
+ return parameter.stream().map(DataPointQuery::toQueryString).collect(Collectors.joining(" AND "));
}
}
diff --git a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/ScheduledDatacheckTriggerProperties.java b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/ScheduledDatacheckTriggerProperties.java
index 37ced4f9..ba0e759b 100644
--- a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/ScheduledDatacheckTriggerProperties.java
+++ b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/datacheck/ScheduledDatacheckTriggerProperties.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import io.redlink.more.studymanager.core.properties.TriggerProperties;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -24,7 +25,7 @@ public Optional getCronSchedule() {
return Optional.ofNullable(this.getString("cronSchedule"));
}
- public Optional> getQueryObject() {return this.getObject("queryObject", new TypeReference<>() {});}
+ public Optional> getQueryObject() {return this.getObject("queryObject", new TypeReference<>() {});}
public Optional getWindow() {
return Optional.ofNullable(this.getLong("window"));
diff --git a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTrigger.java b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTrigger.java
index 3a306f93..2476b44c 100644
--- a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTrigger.java
+++ b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTrigger.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.component.trigger.relative;
import io.redlink.more.studymanager.core.component.Trigger;
@@ -10,15 +18,14 @@
import io.redlink.more.studymanager.core.sdk.MorePlatformSDK;
import io.redlink.more.studymanager.core.sdk.MoreTriggerSDK;
import io.redlink.more.studymanager.core.sdk.schedule.CronSchedule;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class RelativeTimeTrigger extends Trigger {
diff --git a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTriggerFactory.java b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTriggerFactory.java
index 9b96b0c9..da8416a3 100644
--- a/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTriggerFactory.java
+++ b/studymanager-intervention/src/main/java/io/redlink/more/studymanager/component/trigger/relative/RelativeTimeTriggerFactory.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.component.trigger.relative;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
@@ -6,7 +14,6 @@
import io.redlink.more.studymanager.core.properties.model.IntegerValue;
import io.redlink.more.studymanager.core.properties.model.Value;
import io.redlink.more.studymanager.core.sdk.MoreTriggerSDK;
-
import java.util.List;
public class RelativeTimeTriggerFactory extends TriggerFactory {
diff --git a/studymanager-observation/README.md b/studymanager-observation/README.md
new file mode 100644
index 00000000..6d64e315
--- /dev/null
+++ b/studymanager-observation/README.md
@@ -0,0 +1,41 @@
+# More Studymanager Observation
+
+## Data Preview
+
+### How to extend a given observation to show different visualisations
+If you want to modify existing data views for observations or want to add a new data view for a specific observation,
+you need to edit the corresponding observation file under `studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation`
+
+Each Observation (e.g. `PolarVerityObservation` or `QuestionObservation`) has two methods which are necessary for the data view.
+- `listViews` (Returns an array of DataViewInfo objects, which represents all possibilities to show observation data)
+- `getView` (Returns a specific DataView based on the given parameters)
+
+A `private enum DataViewInfoType` defines all possible types as DataViewInfoType and needs to be implemented in the
+corresponding `*Observation.java` file. The name of an enum type is the identifier of a specific view,
+which you need for the request, when you want to receive the data for this DataView.
+
+In the DataViewInfoType, you need to define some information about the data view:
+- The translation keys for the label, title and description. These keys needs to be defined in the studymanager-frontend Repository as well (in de.json and en.json)
+- The chartType (one type of DataView.ChartType enum) which represents the visual representation (e.g. Pie, Bar or Line)
+- The viewConfig which defines how the data will be fetched and aggregated from elastic search
+
+#### ViewConfig (detailed explanation)
+1. The first argument of a ViewConfig record is a List of filters, where you can additionally define filters, which are applied in the elastic query later on (not yet implemented, because currently not needed).
+2. The second argument of a ViewConfig record is an Aggregation, where you can define which data points should be shown. The Aggregation can be one of the enum values (e.g. TIME, STUDY_GROUP, PARTICIPANT or TERM_FIELD).
+3. The third argument of a ViewConfig record is an Aggregation as well, where you can define the aggregation for the x-axis.
+4. The fourth argument of a ViewConfig record is an Operation, where you can define which aggregation function (e.g. AVG, COUNT, SUM) should be applied on which field (e.g. "hr"), which then represents the y-axis.
+
+#### Example (PolarVerityObservation)
+When you request the API endpoint `/studies/{studyId}/observations/{observationId}/views` with the corresponding studyId and observationId (for the Polar Verity Sensor),
+you'll get back a list of all possible views, in this case `["heart_rate"]`.
+Now you can fetch the observation data view information with the API endpoint `/studies/{studyId}/observations/{observationId}/views/{viewName}`, where `viewName` is `heart_rate`.
+
+Explanation of the example screenshot below (chart in the Frontend):
+- "Heart rate" shown as a Tab in the FE, is the "label" defined in `DataViewInfoType`
+- "Average heart rate per part..." shown as the title of the chart, is the "title" defined in `DataViewInfoType`
+- "Compares the average heart rate of all ..." shown under the title of the chart, is the "description" defined in `DataViewInfoType`
+- Each participant alias is shown in the chart legend, and its query definition is defined in the `ViewConfig.rowAggregation`
+- The query definition for the x-axis labels (time) is defined in the `ViewConfig.seriesAggregration`
+- The values (y-axis) for each participant is the average heart rate over time and its query definition is defined in the `ViewConfig.Operation`, where you need to define the corresponding observation data (e.g. "hr") and operation type (e.g. `ViewConfig.Operator.AVG`)
+
+![Heart rate chart](./images/heart_rate_chart.png)
\ No newline at end of file
diff --git a/studymanager-observation/images/heart_rate_chart.png b/studymanager-observation/images/heart_rate_chart.png
new file mode 100644
index 00000000..74929f2f
Binary files /dev/null and b/studymanager-observation/images/heart_rate_chart.png differ
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/PolarVerityObservation.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/PolarVerityObservation.java
index 2682cd3b..ea263056 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/PolarVerityObservation.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/PolarVerityObservation.java
@@ -10,11 +10,94 @@
import io.redlink.more.studymanager.core.component.Observation;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
+import io.redlink.more.studymanager.core.ui.DataView;
+import io.redlink.more.studymanager.core.ui.DataViewData;
+import io.redlink.more.studymanager.core.ui.DataViewInfo;
+import io.redlink.more.studymanager.core.ui.ViewConfig;
+import java.util.List;
public class PolarVerityObservation extends Observation {
public PolarVerityObservation(MoreObservationSDK sdk, C properties) throws ConfigurationValidationException {
super(sdk, properties);
}
+
+ private enum DataViewInfoType implements DataViewInfo {
+ heart_rate("avgHeartRate",
+ DataView.ChartType.LINE,
+ new ViewConfig(
+ List.of(),
+ ViewConfig.Aggregation.PARTICIPANT,
+ ViewConfig.Aggregation.TIME,
+ new ViewConfig.Operation(ViewConfig.Operator.AVG, "hr")
+ )
+ );
+
+ private final String label;
+ private final String title;
+ private final String description;
+ private final DataView.ChartType chartType;
+
+ private final ViewConfig viewConfig;
+
+ DataViewInfoType(String i18nKey, DataView.ChartType chartType, ViewConfig viewConfig) {
+ this(
+ "monitoring.charts.polarVerity.%s.label".formatted(i18nKey),
+ "monitoring.charts.polarVerity.%s.title".formatted(i18nKey),
+ "monitoring.charts.polarVerity.%s.description".formatted(i18nKey),
+ chartType,
+ viewConfig
+ );
+ }
+
+ DataViewInfoType(String label, String title, String description, DataView.ChartType chartType, ViewConfig viewConfig) {
+ this.label = label;
+ this.title = title;
+ this.description = description;
+ this.chartType = chartType;
+ this.viewConfig = viewConfig;
+ }
+
+ @Override
+ public String label() {
+ return this.label;
+ }
+
+ @Override
+ public String title() {
+ return this.title;
+ }
+
+ @Override
+ public String description() {
+ return this.description;
+ }
+
+ public DataView.ChartType chartType() {
+ return chartType;
+ }
+
+ public ViewConfig getViewConfig() {
+ return viewConfig;
+ }
+ }
+
+ public DataViewInfo[] listViews() {
+ return DataViewInfoType.values();
+ }
+
+ @Override
+ public DataView getView(String viewName, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ final DataViewInfoType dataView = DataViewInfoType.valueOf(viewName);
+ final DataViewData dataViewData = sdk.queryData(dataView.getViewConfig(), studyGroupId, participantId, timerange);
+
+ return new DataView(
+ dataView,
+ dataView.chartType(),
+ dataViewData
+ );
+
+ }
}
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservation.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservation.java
index 82a2c992..03034340 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservation.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservation.java
@@ -10,12 +10,169 @@
import io.redlink.more.studymanager.core.component.Observation;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
+import io.redlink.more.studymanager.core.ui.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class QuestionObservation extends Observation {
public QuestionObservation(MoreObservationSDK sdk, C properties) throws ConfigurationValidationException {
super(sdk, properties);
}
+
+ private enum DataViewInfoType implements DataViewInfo {
+ response_distribution(
+ "responseDistribution",
+ DataView.ChartType.PIE,
+ new ViewConfig(
+ List.of(),
+ null,
+ ViewConfig.Aggregation.TERM_FIELD,
+ new ViewConfig.Operation(ViewConfig.Operator.COUNT, "answer")
+ )
+ ),
+ answers_by_group("responseDistributionStudyGroup",
+ DataView.ChartType.BAR,
+ new ViewConfig(
+ List.of(),
+ ViewConfig.Aggregation.TERM_FIELD,
+ ViewConfig.Aggregation.STUDY_GROUP,
+ new ViewConfig.Operation(ViewConfig.Operator.COUNT, "answer")
+ )
+ ),
+ group_by_answers("responseDistributionResponse",
+ DataView.ChartType.BAR,
+ new ViewConfig(
+ List.of(),
+ ViewConfig.Aggregation.STUDY_GROUP,
+ ViewConfig.Aggregation.TERM_FIELD,
+ new ViewConfig.Operation(ViewConfig.Operator.COUNT, "answer")
+ )
+ ),
+ ;
+
+ private final String label;
+ private final String title;
+ private final String description;
+ private final DataView.ChartType chartType;
+
+ private final ViewConfig viewConfig;
+
+ DataViewInfoType(String i18nKey, DataView.ChartType chartType, ViewConfig viewConfig) {
+ this(
+ "monitoring.charts.simpleQuestion.%s.label".formatted(i18nKey),
+ "monitoring.charts.simpleQuestion.%s.title".formatted(i18nKey),
+ "monitoring.charts.simpleQuestion.%s.description".formatted(i18nKey),
+ chartType,
+ viewConfig
+ );
+ }
+
+ DataViewInfoType(String label, String title, String description, DataView.ChartType chartType, ViewConfig viewConfig) {
+ this.label = label;
+ this.title = title;
+ this.description = description;
+ this.chartType = chartType;
+ this.viewConfig = viewConfig;
+ }
+
+ @Override
+ public String label() {
+ return this.label;
+ }
+
+ @Override
+ public String title() {
+ return this.title;
+ }
+
+ @Override
+ public String description() {
+ return this.description;
+ }
+
+ @Override
+ public DataView.ChartType chartType() {
+ return chartType;
+ }
+
+ public ViewConfig getViewConfig() {
+ return viewConfig;
+ }
+ }
+
+ @Override
+ public DataViewInfo[] listViews() {
+ return DataViewInfoType.values();
+ }
+
+ @Override
+ public DataView getView(String viewName, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ final DataViewInfoType dataView = DataViewInfoType.valueOf(viewName);
+ final DataViewData dataViewData = sdk.queryData(dataView.getViewConfig(), studyGroupId, participantId, timerange);
+
+ return new DataView(
+ dataView,
+ dataView.chartType(),
+ addMissingAnswerOptions(dataView, dataViewData)
+ );
+
+ }
+
+ private DataViewData addMissingAnswerOptions(DataViewInfoType dataView, DataViewData dataViewData) {
+ ArrayList allPossibleAnswerOptions = (ArrayList) this.properties.get("answers");
+ List labels = new ArrayList<>(dataViewData.labels());
+ List rows = new ArrayList<>(dataViewData.rows());
+
+ switch (dataView) {
+ case response_distribution:
+ if (allPossibleAnswerOptions.size() == dataViewData.labels().size() || dataViewData.labels().isEmpty()) {
+ return dataViewData;
+ }
+ for (String item : allPossibleAnswerOptions) {
+ if (!labels.contains(item)) {
+ labels.add(item);
+ if (!rows.isEmpty()) {
+ rows.get(0).values().add(0.0);
+ }
+ }
+ }
+ break;
+ case answers_by_group:
+ if (allPossibleAnswerOptions.size() == dataViewData.rows().size() || dataViewData.rows().isEmpty()) {
+ return dataViewData;
+ }
+ for (String item : allPossibleAnswerOptions) {
+ if (rows.stream().noneMatch(v -> v.label().equals(item))) {
+ ArrayList values = Stream.generate(() -> (Double) null)
+ .limit(labels.size()).collect(Collectors.toCollection(ArrayList::new));
+
+ rows.add(new DataViewRow(item, values));
+ }
+ }
+ break;
+ case group_by_answers:
+ if (allPossibleAnswerOptions.size() == dataViewData.labels().size() || dataViewData.labels().isEmpty()) {
+ return dataViewData;
+ }
+
+ for (String item : allPossibleAnswerOptions) {
+ if (!labels.contains(item)) {
+ labels.add(item);
+ for (DataViewRow row : rows) {
+ row.values().add(0.0);
+ }
+ }
+ }
+ break;
+ }
+
+ return new DataViewData(labels, rows);
+ }
}
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservationFactory.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservationFactory.java
index 875d56cd..92064549 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservationFactory.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/QuestionObservationFactory.java
@@ -44,6 +44,7 @@ public class QuestionObservationFactory, P extends Obse
"Yes"
))
);
+
@Override
public String getId() {
return "question-observation";
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java
index 8d672ea9..63953304 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java
@@ -13,6 +13,10 @@
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
import io.redlink.more.studymanager.core.sdk.MorePlatformSDK;
+import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport;
+import io.redlink.more.studymanager.core.validation.ValidationIssue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Optional;
@@ -22,6 +26,7 @@ public class LimeSurveyObservation extends Obse
public static final String LIME_SURVEY_ID = "limeSurveyId";
private final LimeSurveyRequestService limeSurveyRequestService;
+ private static final Logger LOGGER = LoggerFactory.getLogger(LimeSurveyObservation.class);
public LimeSurveyObservation(MoreObservationSDK sdk, C properties, LimeSurveyRequestService limeSurveyRequestService) throws ConfigurationValidationException {
super(sdk, properties);
@@ -35,15 +40,15 @@ public void activate(){
Set participantIds = sdk.participantIds(MorePlatformSDK.ParticipantFilter.ALL);
participantIds.removeIf(id -> sdk.getPropertiesForParticipant(id).isPresent());
limeSurveyRequestService.activateParticipants(participantIds, surveyId)
- .forEach(data -> {
+ .forEach(data ->
sdk.setPropertiesForParticipant(
Integer.parseInt(data.firstname()),
new ObservationProperties(
Map.of("token", data.token(),
"limeUrl", limeSurveyRequestService.getBaseUrl())
)
- );
- });
+ )
+ );
limeSurveyRequestService.setSurveyEndUrl(surveyId, sdk.getStudyId(), sdk.getObservationId());
limeSurveyRequestService.activateSurvey(surveyId);
sdk.setValue(LIME_SURVEY_ID, surveyId);
@@ -52,21 +57,19 @@ public void activate(){
protected String checkAndGetSurveyId() {
String newSurveyId = properties.getString(LIME_SURVEY_ID);
String activeSurveyId = sdk.getValue(LIME_SURVEY_ID, String.class).orElse(null);
-
- if(activeSurveyId != null && !activeSurveyId.equals(newSurveyId)) {
- throw new RuntimeException(String.format(
+ if (activeSurveyId != null && !activeSurveyId.equals(newSurveyId)) {
+ LOGGER.error(String.format(
"SurveyId on Observation %s must not be changed: %s -> %s",
sdk.getObservationId(),
activeSurveyId,
newSurveyId
));
+ throw new ConfigurationValidationException(ConfigurationValidationReport.of(ValidationIssue.immutablePropertyChanged(LimeSurveyObservationFactory.limeSurveyId)));
} else {
return newSurveyId;
}
}
-
-
@Override
public void deactivate() {
// for downwards compatibility (already running studies)
@@ -80,21 +83,22 @@ public void deactivate() {
public boolean writeDataPoints(String token, int surveyId, int savedId) {
//check if token exists, get participant and answer and store as datapoint
- getParticipantForToken(token).ifPresent(participantId -> {
- limeSurveyRequestService.getAnswer(token, surveyId, savedId).ifPresent(m -> {
- sdk.storeDataPoint(participantId, "lime-survey-observation", m);
- });
- });
+ getParticipantForToken(token).ifPresent(participantId ->
+ limeSurveyRequestService.getAnswer(token, surveyId, savedId).ifPresent(m ->
+ sdk.storeDataPoint(participantId, "lime-survey-observation", m)
+ )
+ );
return true;
}
public Optional getParticipantForToken(String token) {
- return sdk.participantIds(MorePlatformSDK.ParticipantFilter.ALL).stream().filter(id -> {
- return sdk.getPropertiesForParticipant(id)
- .map(o -> o.getString("token"))
- .map(t -> t.equals(token))
- .orElse(false);
- }).findFirst();
-
+ return sdk.participantIds(MorePlatformSDK.ParticipantFilter.ALL).stream()
+ .filter(id ->
+ sdk.getPropertiesForParticipant(id)
+ .map(o -> o.getString("token"))
+ .map(t -> t.equals(token))
+ .orElse(false)
+ )
+ .findFirst();
}
}
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java
index 7aa842a1..a4d158e4 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java
@@ -20,13 +20,18 @@
import io.redlink.more.studymanager.core.properties.model.StringValue;
import io.redlink.more.studymanager.core.properties.model.Value;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
-
import java.util.List;
import java.util.Optional;
public class LimeSurveyObservationFactory, P extends ObservationProperties>
extends ObservationFactory {
+ public static final Value limeSurveyId = new StringValue("limeSurveyId")
+ .setName("observation.factory.limeSurvey.configProps.idName")
+ .setDescription("observation.factory.limeSurvey.configProps.idDesc")
+ .setRequired(true)
+ .setImmutable(true);
+
private static final List properties = List.of(
/* TODO enable Autocomplete in FE
new AutocompleteValue("limeSurveyId", "surveys")
@@ -34,10 +39,7 @@ public class LimeSurveyObservationFactory, P
.setDescription("An existing survey")
.setRequired(true)
*/
- new StringValue("limeSurveyId")
- .setName("observation.factory.limeSurvey.configProps.idName")
- .setDescription("observation.factory.limeSurvey.configProps.idDesc")
- .setRequired(true)
+ limeSurveyId
);
private LimeSurveyRequestService limeSurveyRequestService;
diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyRequestService.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyRequestService.java
index 1a63b16f..4a3232f8 100644
--- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyRequestService.java
+++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyRequestService.java
@@ -140,8 +140,10 @@ private List createParticipants(Set participantIds, St
String rsp = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
- if(rsp.contains("Error: Invalid survey ID")) {
+ if (rsp.contains("Error: Invalid survey ID")) {
throw new RuntimeException("Invalid survey ID: " + surveyId);
+ } else if (rsp.contains("Invalid session key")) {
+ throw new RuntimeException("Connection to Limesurvey failed");
}
List data = mapper.readValue(
@@ -159,17 +161,24 @@ private List createParticipants(Set participantIds, St
}
}
- private String getSessionKey(){
+ private String getSessionKey() {
try {
HttpRequest request = createHttpRequest(
parseRequest("get_session_key",
List.of(properties.get("username"), properties.get("password")))
);
- return mapper.readValue(
- client.send(request, HttpResponse.BodyHandlers.ofString()).body(),
- LimeSurveyObjectResponse.class)
- .result().toString();
- } catch(IOException | InterruptedException e){
+ var rsp = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
+ var values = mapper.readValue(rsp, LimeSurveyObjectResponse.class);
+ var result = values.result().toString();
+
+ if (result.contains("Invalid user name or password")) {
+ throw new RuntimeException("Not possible to get session key for Limesurvey because of invalid credentials.");
+ } else if (result.contains("You have exceeded the number of maximum login attempts. Please wait 10 minutes before trying again")) {
+ throw new RuntimeException("Too many login attempts for Limesurvey. Try again in 10 minutes.");
+ }
+
+ return result;
+ } catch(IOException | InterruptedException e) {
LOGGER.error("Error getting session key for Limesurvey remote control");
throw new RuntimeException(e);
}
diff --git a/studymanager/pom.xml b/studymanager/pom.xml
index 021a90bc..dce76ae4 100644
--- a/studymanager/pom.xml
+++ b/studymanager/pom.xml
@@ -159,7 +159,7 @@
net.logstash.logback
logstash-logback-encoder
- 7.4
+ 8.0
runtime
@@ -227,7 +227,7 @@
org.openapitools
openapi-generator-maven-plugin
- 6.6.0
+ 7.7.0
configurator-api
@@ -235,56 +235,51 @@
generate
-
- true
-
+ spring
+ spring-boot
+
${project.basedir}/src/main/resources/openapi/StudyManagerAPI.yaml
+
+ io.redlink.more.studymanager.api.v1
+ true
io.redlink.more.studymanager.api.v1.webservices
+ true
io.redlink.more.studymanager.api.v1.model
+ DTO
+
+ false
+ true
+ false
+ false
+
-
+ string+date-time=Instant
string+time=LocalTime
Instant=java.time.Instant
LocalTime=java.time.LocalTime
+ DataExport=org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
Instant=java.time.Instant
LocalTime=java.time.LocalTime
+
+
+ true
+ src
+ source
+ true
+ true
+ false
+ true
+ api_interface
+
-
- spring
- spring-boot
-
- io.redlink.more.studymanager.api.v1
-
- true
- io.redlink.more.studymanager.api.v1.webservices
-
- true
- io.redlink.more.studymanager.api.v1.model
- DTO
-
- false
- true
- false
- false
-
-
- true
- src
- source
- true
- false
- false
- true
-
-
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/SessionConfiguration.java b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/SessionConfiguration.java
index 4a363524..2e4305d3 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/SessionConfiguration.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/SessionConfiguration.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.configuration;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/WebSecurityConfiguration.java b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/WebSecurityConfiguration.java
index 5aa7bc21..cdf54107 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/configuration/WebSecurityConfiguration.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/configuration/WebSecurityConfiguration.java
@@ -11,19 +11,24 @@
import io.redlink.more.studymanager.properties.MoreAuthProperties;
import io.redlink.more.studymanager.repository.UserRepository;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
@@ -32,9 +37,6 @@
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
@@ -50,6 +52,7 @@
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.IpAddressMatcher;
+import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@Configuration
@EnableWebSecurity
@@ -71,20 +74,24 @@ protected SecurityFilterChain filterChain(HttpSecurity http,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
UserRepository userRepository) throws Exception {
// Basics
- http.csrf()
+ http.csrf(csrf -> csrf
.ignoringRequestMatchers("/kibana/**")
- .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
- http.cors().disable();
+ .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+ );
+ http.cors(AbstractHttpConfigurer::disable);
// Restricted Paths
- http.authorizeHttpRequests()
- .requestMatchers("/api", "/api/v1/me").permitAll()
+ http.authorizeHttpRequests(auth -> auth
+ .requestMatchers(HttpMethod.GET, "/api", "/api/v1/me").permitAll()
// Allow unauthenticated access to the ui-/auth-settings
.requestMatchers(HttpMethod.GET, "/api/v1/config/ui").permitAll()
+ // Allow unauthenticated access to the build-info
+ .requestMatchers(HttpMethod.GET, "/api/v1/config/buildInfo").permitAll()
//TODO specific handling of temporary sidecar
.requestMatchers("/api/v1/components/observation/lime-survey-observation/end.html").permitAll()
- .requestMatchers("/api/v1/studies/*/export/studydata/*").permitAll()
- .requestMatchers("/api/v1/studies/*/calendar.ics").permitAll()
+ // Study-Data-Export is authenticated internally using individual access-tokens
+ .requestMatchers(HttpMethod.GET, "/api/v1/studies/*/export/studydata/*").permitAll()
+ .requestMatchers(HttpMethod.GET, "/api/v1/studies/*/calendar.ics").permitAll()
.requestMatchers("/api/v1/**").authenticated()
.requestMatchers("/kibana/**").authenticated()
.requestMatchers("/login/init").authenticated()
@@ -96,30 +103,44 @@ protected SecurityFilterChain filterChain(HttpSecurity http,
)
).permitAll()
.requestMatchers("/error").authenticated()
- .anyRequest().denyAll();
+ .anyRequest().denyAll()
+ );
// API-Calls should not be redirected to the login page, but answered with a 401
- http.exceptionHandling()
- .defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new AntPathRequestMatcher("/api/**"));
+ http.exceptionHandling(exHandling -> exHandling
+ .defaultAuthenticationEntryPointFor(
+ new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
+ new AndRequestMatcher(
+ new AntPathRequestMatcher("/api/**"),
+ new NegatedRequestMatcher(
+ new AntPathRequestMatcher("/api/v1/studies/*/export/studydata/*")
+ )
+ )
+ )
+ );
// Logout Config
- http.logout()
+ http.logout(logout -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
- .logoutSuccessUrl("/");
+ .logoutSuccessUrl("/")
+ );
// Enable OAuth2
- http.oauth2Login()
+ http.oauth2Login(oauth -> oauth
// register oauth2-provider under this baseurl to simplify routing
- .authorizationEndpoint().baseUri("/login/oauth").and()
+ .authorizationEndpoint(ep -> ep.baseUri("/login/oauth"))
.authorizedClientService(
new UserSyncingOAuth2AuthorizedClientService(oAuth2AuthorizedClientService, oAuth2AuthenticationService, userRepository)
- );
+ )
+ );
// Enable OAuth2 client_credentials flow (insomnia)
- http.oauth2ResourceServer().jwt();
+ http.oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
//TODO maybe disable in production
- http.headers().frameOptions().disable();
+ http.headers(headers -> headers
+ .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
+ );
return http.build();
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/LoginController.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/LoginController.java
index 767cf89b..5474bba6 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/LoginController.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/LoginController.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/RequiresStudyRole.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/RequiresStudyRole.java
index 13e40b9a..42637178 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/RequiresStudyRole.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/RequiresStudyRole.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2023 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/RootController.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/RootController.java
index 487180d7..8780576e 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/RootController.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/RootController.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CalendarApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CalendarApiV1Controller.java
index 2093ad09..31d1d4ff 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CalendarApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CalendarApiV1Controller.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.controller.studymanager;
import io.redlink.more.studymanager.api.v1.model.StudyTimelineDTO;
@@ -7,8 +15,8 @@
import io.redlink.more.studymanager.model.transformer.TimelineTransformer;
import io.redlink.more.studymanager.properties.GatewayProperties;
import io.redlink.more.studymanager.service.CalendarService;
+import java.time.Instant;
import java.time.LocalDate;
-import java.time.OffsetDateTime;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -38,7 +46,7 @@ public ResponseEntity getStudyCalendar(Long studyId) {
@Override
@RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_OPERATOR})
- public ResponseEntity getStudyTimeline(Long studyId, Integer participant, Integer studyGroup, OffsetDateTime referenceDate, LocalDate from, LocalDate to) {
+ public ResponseEntity getStudyTimeline(Long studyId, Integer participant, Integer studyGroup, Instant referenceDate, LocalDate from, LocalDate to) {
return ResponseEntity.ok(
TimelineTransformer.toStudyTimelineDTO(
service.getTimeline(studyId, participant, studyGroup, referenceDate, from, to)
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CollaboratorsApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CollaboratorsApiV1Controller.java
index 57242f64..9ddc8f8e 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CollaboratorsApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/CollaboratorsApiV1Controller.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller.studymanager;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ComponentApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ComponentApiV1Controller.java
index f86e082d..41b33df6 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ComponentApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ComponentApiV1Controller.java
@@ -9,7 +9,10 @@
package io.redlink.more.studymanager.controller.studymanager;
import com.fasterxml.jackson.databind.JsonNode;
-import io.redlink.more.studymanager.api.v1.model.*;
+import io.redlink.more.studymanager.api.v1.model.ComponentFactoryDTO;
+import io.redlink.more.studymanager.api.v1.model.ComponentFactoryMeasurementsInnerDTO;
+import io.redlink.more.studymanager.api.v1.model.ValidationReportDTO;
+import io.redlink.more.studymanager.api.v1.model.VisibilityDTO;
import io.redlink.more.studymanager.api.v1.webservices.ComponentsApi;
import io.redlink.more.studymanager.core.exception.ApiCallException;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
@@ -21,19 +24,19 @@
import io.redlink.more.studymanager.core.model.User;
import io.redlink.more.studymanager.core.properties.ComponentProperties;
import io.redlink.more.studymanager.core.webcomponent.WebComponent;
+import io.redlink.more.studymanager.model.transformer.ValidationReportTransformer;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
import io.redlink.more.studymanager.utils.MapperUtils;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
@RestController
@RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class ComponentApiV1Controller implements ComponentsApi {
@@ -73,19 +76,11 @@ public ResponseEntity validateProperties(String componentTy
f.validate((ComponentProperties) MapperUtils.MAPPER.convertValue(body, f.getPropertyClass()));
return new ValidationReportDTO().valid(true);
} catch (ConfigurationValidationException e) {
- return new ValidationReportDTO()
- .valid(false)
- .errors(e.getReport().getErrors().stream()
- .map(i -> new ValidationReportItemDTO().message(i.getMessage()).propertyId(i.getPropertyId()).type("error"))
- .toList()
- ).warnings(e.getReport().getWarnings().stream()
- .map(i -> new ValidationReportItemDTO().message(i.getMessage()).propertyId(i.getPropertyId()).type("warning"))
- .toList()
- );
+ return ValidationReportTransformer.validationReportDTO_V1(e);
}
})
.map(ResponseEntity::ok)
- .orElse(ResponseEntity.notFound().build());
+ .orElseGet(() -> ResponseEntity.notFound().build());
}
@Override
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ConfigurationApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ConfigurationApiV1Controller.java
index 734946d8..d26dbb1c 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ConfigurationApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ConfigurationApiV1Controller.java
@@ -1,12 +1,19 @@
/*
- * Copyright (c) 2023 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller.studymanager;
+import io.redlink.more.studymanager.api.v1.model.BuildInfoDTO;
import io.redlink.more.studymanager.api.v1.model.FrontendConfigurationDTO;
import io.redlink.more.studymanager.api.v1.model.KeycloakSettingsDTO;
import io.redlink.more.studymanager.api.v1.webservices.ConfigurationApi;
import io.redlink.more.studymanager.properties.FrontendConfigurationProperties;
+import java.time.Instant;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -24,6 +31,18 @@ public ConfigurationApiV1Controller(FrontendConfigurationProperties uiConfig) {
this.uiConfig = uiConfig;
}
+ @Override
+ public ResponseEntity getBuildInfo() {
+ return ResponseEntity.ok(
+ new BuildInfoDTO(
+ "null",
+ Instant.EPOCH
+ )
+ .branch("undefined")
+ .rev("unknown")
+ );
+ }
+
@Override
public ResponseEntity getFrontendConfig() {
return ResponseEntity.ok(
@@ -32,13 +51,13 @@ public ResponseEntity getFrontendConfig() {
}
private static FrontendConfigurationDTO transform(FrontendConfigurationProperties uiConfig) {
- return new FrontendConfigurationDTO()
+ return new FrontendConfigurationDTO(
+ new KeycloakSettingsDTO(
+ uiConfig.keycloak().server(),
+ uiConfig.keycloak().realm(),
+ uiConfig.keycloak().clientId()
+ ))
.title(uiConfig.title())
- .auth(new KeycloakSettingsDTO()
- .server(uiConfig.keycloak().server())
- .realm(uiConfig.keycloak().realm())
- .clientId(uiConfig.keycloak().clientId())
- )
;
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/DataApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/DataApiV1Controller.java
index f884d8e1..b1d0dfc2 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/DataApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/DataApiV1Controller.java
@@ -9,21 +9,22 @@
package io.redlink.more.studymanager.controller.studymanager;
import io.redlink.more.studymanager.api.v1.model.DataPointDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationDataViewDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationDataViewDataDTO;
import io.redlink.more.studymanager.api.v1.model.ParticipationDataDTO;
import io.redlink.more.studymanager.api.v1.webservices.DataApi;
import io.redlink.more.studymanager.controller.RequiresStudyRole;
import io.redlink.more.studymanager.model.StudyRole;
import io.redlink.more.studymanager.model.transformer.StudyDataTransformer;
import io.redlink.more.studymanager.service.DataProcessingService;
-
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import java.time.OffsetDateTime;
-import java.util.List;
-
@RestController
@RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class DataApiV1Controller implements DataApi {
@@ -35,7 +36,7 @@ public class DataApiV1Controller implements DataApi {
@Override
@RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_VIEWER})
public ResponseEntity> getDataPoints(
- Long studyId, Integer size, Integer observationId, Integer participantId, OffsetDateTime date
+ Long studyId, Integer size, Integer observationId, Integer participantId, Instant date
) {
return ResponseEntity.ok().body(
dataProcessingService.getDataPoints(studyId, size, observationId, participantId, date)
@@ -51,4 +52,24 @@ public ResponseEntity> getParticipationData(Long stud
.toList()
);
}
+
+ @Override
+ @RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_VIEWER})
+ public ResponseEntity getObservationViewData(Long studyId, Integer observationId, String viewName, Integer studyGroupId, Integer participantId, Instant from, Instant to) {
+ return ResponseEntity.ok().body(
+ StudyDataTransformer.toObservationDataViewDataDTO(
+ dataProcessingService.getDataView(studyId, observationId, viewName, studyGroupId, participantId, from, to)
+ )
+ );
+ }
+
+ @Override
+ @RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_VIEWER})
+ public ResponseEntity> listObservationViews(Long studyId, Integer observationId) {
+ return ResponseEntity.ok().body(
+ Arrays.stream(dataProcessingService.listDataViews(studyId, observationId))
+ .map(StudyDataTransformer::toObservationDataViewDTO)
+ .toList()
+ );
+ }
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ExceptionControllerAdvice.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ExceptionControllerAdvice.java
new file mode 100644
index 00000000..5f95bc96
--- /dev/null
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ExceptionControllerAdvice.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.controller.studymanager;
+
+import io.redlink.more.studymanager.api.v1.model.ValidationReportDTO;
+import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
+import io.redlink.more.studymanager.model.transformer.ValidationReportTransformer;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice(basePackageClasses = ExceptionControllerAdvice.class)
+public class ExceptionControllerAdvice {
+
+ @ExceptionHandler(ConfigurationValidationException.class)
+ public ResponseEntity handleConfigurationValidationException(ConfigurationValidationException e) {
+ return ResponseEntity.status(HttpStatus.CONFLICT)
+ .header("Error-Message", e.getMessage())
+ .body(ValidationReportTransformer.validationReportDTO_V1(e));
+ }
+
+}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ImportExportApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ImportExportApiV1Controller.java
index af8a46dc..c5978075 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ImportExportApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/ImportExportApiV1Controller.java
@@ -21,20 +21,29 @@
import io.redlink.more.studymanager.service.ImportExportService;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
import io.redlink.more.studymanager.utils.MapperUtils;
-import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.io.IOException;
+import java.time.Instant;
+import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping(value = "/api/v1")
public class ImportExportApiV1Controller implements ImportExportApi {
+ private static final Logger LOGGER = LoggerFactory.getLogger(StudyApiV1Controller.class);
private final ImportExportService service;
@@ -84,28 +93,38 @@ public ResponseEntity exportStudy(Long studyId) {
}
@Override
- @RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_OPERATOR})
- public ResponseEntity generateDownloadToken(Long studyId) {
- return ResponseEntity.ok(new GenerateDownloadToken200ResponseDTO().token(
- tokenRepository.createToken(studyId).getToken()
- ));
- }
-
- @RequestMapping(
- method = RequestMethod.GET,
- value = "/studies/{studyId}/export/studydata/{token}",
- produces = { "application/json" }
- )
- public void exportStudyData(@PathVariable Long studyId, @PathVariable("token") String token, HttpServletResponse response) throws IOException {
+ public ResponseEntity exportStudyData(Long studyId, String token, List studyGroupId, List participantId, List observationId, Instant from, Instant to) {
Optional dt = tokenRepository.getToken(token).filter(t -> t.getStudyId().equals(studyId));
- if(dt.isPresent()) {
- response.setHeader("Content-Disposition", "attachment;filename=" + dt.get().getFilename());
- service.exportStudyData(response.getOutputStream(), studyId);
+
+ if (dt.isPresent()) {
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.set("Content-Disposition", "attachment;filename=" + dt.get().getFilename());
+
+ return ResponseEntity
+ .ok()
+ .headers(responseHeaders)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(outputStream -> {
+ try {
+ service.exportStudyData(outputStream, studyId, studyGroupId, participantId, observationId, from, to);
+ } catch (Exception e) {
+ LOGGER.warn("Error exporting study data for study_{}: {}", studyId, e.getMessage(), e);
+ }
+ });
} else {
- response.setStatus(403);
+ return ResponseEntity.notFound().build();
}
}
+ @Override
+ @RequiresStudyRole({StudyRole.STUDY_ADMIN, StudyRole.STUDY_OPERATOR})
+ public ResponseEntity generateDownloadToken(Long studyId, List studyGroupId, List participantId, List observationId, Instant from, Instant to) {
+ var token = tokenRepository.createToken(studyId).getToken();
+ var uri = ServletUriComponentsBuilder.fromCurrentRequest().pathSegment(token).build(true).toUri();
+
+ return ResponseEntity.created(uri).body(new GenerateDownloadToken200ResponseDTO().token(token));
+ }
+
@Override
public ResponseEntity importStudy(MultipartFile file) {
try {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/StudyApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/StudyApiV1Controller.java
index 74e0edf4..154b986e 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/StudyApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/StudyApiV1Controller.java
@@ -17,6 +17,7 @@
import io.redlink.more.studymanager.model.transformer.StudyTransformer;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
import io.redlink.more.studymanager.service.StudyService;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -25,8 +26,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import java.util.List;
-
@RestController
@RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class StudyApiV1Controller implements StudiesApi {
@@ -92,9 +91,12 @@ public ResponseEntity deleteStudy(Long studyId) {
@Override
@RequiresStudyRole(StudyRole.STUDY_ADMIN)
- public ResponseEntity setStatus(Long studyId, StatusChangeDTO statusChangeDTO) {
+ public ResponseEntity setStatus(Long studyId, StatusChangeDTO statusChangeDTO) {
final var currentUser = authService.getCurrentUser();
- service.setStatus(studyId, StudyTransformer.fromStatusChangeDTO_V1(statusChangeDTO), currentUser);
- return ResponseEntity.ok().build();
+
+ return ResponseEntity.of(
+ service.setStatus(studyId, StudyTransformer.fromStatusChangeDTO_V1(statusChangeDTO), currentUser)
+ .map(StudyTransformer::toStudyDTO_V1)
+ );
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/UserApiV1Controller.java b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/UserApiV1Controller.java
index f6536330..d7e29e3f 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/UserApiV1Controller.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/controller/studymanager/UserApiV1Controller.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.controller.studymanager;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/exception/AccessDeniedException.java b/studymanager/src/main/java/io/redlink/more/studymanager/exception/AccessDeniedException.java
index 3ec6206f..ccb090ec 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/exception/AccessDeniedException.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/exception/AccessDeniedException.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.exception;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/exception/DataConstraintException.java b/studymanager/src/main/java/io/redlink/more/studymanager/exception/DataConstraintException.java
index 1eeebf65..fbb2f608 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/exception/DataConstraintException.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/exception/DataConstraintException.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.exception;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/AttributeMapClaimAccessor.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/AttributeMapClaimAccessor.java
index 70e9846a..e25f69dc 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/AttributeMapClaimAccessor.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/AttributeMapClaimAccessor.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/IntegrationInfo.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/IntegrationInfo.java
index 10964a8d..05b641ac 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/IntegrationInfo.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/IntegrationInfo.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model;
public record IntegrationInfo(
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/PlatformRole.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/PlatformRole.java
index 9206ff07..b9937484 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/PlatformRole.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/PlatformRole.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/StudyRole.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/StudyRole.java
index 3b767c01..74d7f034 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/StudyRole.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/StudyRole.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/User.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/User.java
index 9c4c9786..6a8862ba 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/User.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/User.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/ParticipationData.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/data/ParticipationData.java
similarity index 97%
rename from studymanager/src/main/java/io/redlink/more/studymanager/model/ParticipationData.java
rename to studymanager/src/main/java/io/redlink/more/studymanager/model/data/ParticipationData.java
index 5784ddfc..002b07c9 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/ParticipationData.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/data/ParticipationData.java
@@ -6,7 +6,7 @@
* Förderung der wissenschaftlichen Forschung).
* Licensed under the Elastic License 2.0.
*/
-package io.redlink.more.studymanager.model;
+package io.redlink.more.studymanager.model.data;
import java.time.Instant;
import java.util.Comparator;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/generator/RandomTokenGenerator.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/generator/RandomTokenGenerator.java
index 52f1f2ad..24ce34ed 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/generator/RandomTokenGenerator.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/generator/RandomTokenGenerator.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model.generator;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Duration.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Duration.java
index 9cd086c7..a9788b70 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Duration.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Duration.java
@@ -1,12 +1,17 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.redlink.more.studymanager.api.v1.model.DurationDTO;
import io.redlink.more.studymanager.api.v1.model.StudyDurationDTO;
-
-import java.time.Instant;
-import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Event.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Event.java
index 2f09efdf..664cf972 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Event.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/Event.java
@@ -1,7 +1,14 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonFormat;
-
import java.time.Instant;
public class Event implements ScheduleEvent {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RecurrenceRule.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RecurrenceRule.java
index 9459a9dd..cd9fec80 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RecurrenceRule.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RecurrenceRule.java
@@ -1,7 +1,14 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonFormat;
-
import java.time.Instant;
import java.util.List;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeDate.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeDate.java
index 071beb0c..1048ec5c 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeDate.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeDate.java
@@ -1,8 +1,15 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeEvent.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeEvent.java
index 7fe2a688..317f0532 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeEvent.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeEvent.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeRecurrenceRule.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeRecurrenceRule.java
index abfaf462..df3f4a9f 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeRecurrenceRule.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/RelativeRecurrenceRule.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
public class RelativeRecurrenceRule {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/ScheduleEvent.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/ScheduleEvent.java
index 079867d0..56121c0a 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/ScheduleEvent.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/scheduler/ScheduleEvent.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.scheduler;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/InterventionTimelineEvent.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/InterventionTimelineEvent.java
index bb98ffb1..cbe6e312 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/InterventionTimelineEvent.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/InterventionTimelineEvent.java
@@ -1,8 +1,15 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.timeline;
import io.redlink.more.studymanager.model.Intervention;
import io.redlink.more.studymanager.model.Trigger;
-
import java.time.Instant;
public record InterventionTimelineEvent(
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/ObservationTimelineEvent.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/ObservationTimelineEvent.java
index fa633ae2..aa56e559 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/ObservationTimelineEvent.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/ObservationTimelineEvent.java
@@ -1,7 +1,14 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.timeline;
import io.redlink.more.studymanager.model.Observation;
-
import java.time.Instant;
public record ObservationTimelineEvent(
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/StudyTimeline.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/StudyTimeline.java
index f1392c7d..ad896530 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/StudyTimeline.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/StudyTimeline.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.timeline;
import java.time.Instant;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/TimelineFilter.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/TimelineFilter.java
index be22d630..7cd350d5 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/TimelineFilter.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/timeline/TimelineFilter.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.timeline;
public record TimelineFilter (
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ActionTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ActionTransformer.java
index e8996162..b9f7f265 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ActionTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ActionTransformer.java
@@ -12,6 +12,7 @@
import io.redlink.more.studymanager.core.properties.ActionProperties;
import io.redlink.more.studymanager.model.Action;
import io.redlink.more.studymanager.utils.MapperUtils;
+import java.time.Instant;
public final class ActionTransformer {
@@ -26,12 +27,14 @@ public static Action fromActionDTO_V1(ActionDTO dto) {
}
public static ActionDTO toActionDTO_V1(Action action) {
+ Instant instant = action.getModified();
+ Instant instant1 = action.getCreated();
return new ActionDTO()
.actionId(action.getActionId())
.type(action.getType())
.properties(action.getProperties())
- .created(Transformers.toOffsetDateTime(action.getCreated()))
- .modified(Transformers.toOffsetDateTime(action.getModified()));
+ .created(instant1)
+ .modified(instant);
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ContactTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ContactTransformer.java
index d9e4406c..95897f79 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ContactTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ContactTransformer.java
@@ -11,7 +11,7 @@
import io.redlink.more.studymanager.api.v1.model.ContactDTO;
import io.redlink.more.studymanager.model.Contact;
-public class ContactTransformer {
+public final class ContactTransformer {
public static ContactDTO toContactDTO_V1(Contact contact) {
return new ContactDTO()
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EndpointTokenTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EndpointTokenTransformer.java
index fd5a79d4..14b0aa54 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EndpointTokenTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EndpointTokenTransformer.java
@@ -10,20 +10,21 @@
import io.redlink.more.studymanager.api.v1.model.EndpointTokenDTO;
import io.redlink.more.studymanager.model.EndpointToken;
-
+import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
-public class EndpointTokenTransformer {
+public final class EndpointTokenTransformer {
private EndpointTokenTransformer() {}
public static EndpointToken fromEndpointTokenDTO(EndpointTokenDTO dto) {
+ Instant offsetDateTime = dto.getCreated();
return new EndpointToken(
dto.getTokenId(),
dto.getTokenLabel(),
- Transformers.toInstant(dto.getCreated()),
+ offsetDateTime,
dto.getToken()
);
}
@@ -42,7 +43,7 @@ public static EndpointTokenDTO toEndpointTokenDTO(EndpointToken token) {
return new EndpointTokenDTO()
.tokenId(token.tokenId())
.tokenLabel(token.tokenLabel())
- .created(Transformers.toOffsetDateTime(token.created()))
+ .created(token.created())
.token(token.token());
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EventTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EventTransformer.java
index e653a0b3..f7246864 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EventTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/EventTransformer.java
@@ -8,8 +8,23 @@
*/
package io.redlink.more.studymanager.model.transformer;
-import io.redlink.more.studymanager.api.v1.model.*;
-import io.redlink.more.studymanager.model.scheduler.*;
+import io.redlink.more.studymanager.api.v1.model.DurationDTO;
+import io.redlink.more.studymanager.api.v1.model.EventDTO;
+import io.redlink.more.studymanager.api.v1.model.FrequencyDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationScheduleDTO;
+import io.redlink.more.studymanager.api.v1.model.RecurrenceRuleDTO;
+import io.redlink.more.studymanager.api.v1.model.RelativeDateDTO;
+import io.redlink.more.studymanager.api.v1.model.RelativeEventDTO;
+import io.redlink.more.studymanager.api.v1.model.RelativeRecurrenceRuleDTO;
+import io.redlink.more.studymanager.api.v1.model.WeekdayDTO;
+import io.redlink.more.studymanager.model.scheduler.Duration;
+import io.redlink.more.studymanager.model.scheduler.Event;
+import io.redlink.more.studymanager.model.scheduler.RecurrenceRule;
+import io.redlink.more.studymanager.model.scheduler.RelativeDate;
+import io.redlink.more.studymanager.model.scheduler.RelativeEvent;
+import io.redlink.more.studymanager.model.scheduler.RelativeRecurrenceRule;
+import io.redlink.more.studymanager.model.scheduler.ScheduleEvent;
+import java.time.Instant;
public final class EventTransformer {
@@ -20,9 +35,11 @@ public static ScheduleEvent fromObservationScheduleDTO_V1(ObservationScheduleDTO
if (genericDto != null) {
if(genericDto.getType() == null || Event.TYPE.equals(genericDto.getType())) {
EventDTO dto = (EventDTO) genericDto;
+ Instant offsetDateTime = dto.getDtend();
+ Instant offsetDateTime1 = dto.getDtstart();
return new Event()
- .setDateStart(Transformers.toInstant(dto.getDtstart()))
- .setDateEnd(Transformers.toInstant(dto.getDtend()))
+ .setDateStart(offsetDateTime1)
+ .setDateEnd(offsetDateTime)
.setRRule(fromRecurrenceRuleDTO(dto.getRrule()));
} else if(RelativeEvent.TYPE.equals(genericDto.getType())) {
RelativeEventDTO dto = (RelativeEventDTO) genericDto;
@@ -46,10 +63,12 @@ public static ObservationScheduleDTO toObservationScheduleDTO_V1(ScheduleEvent e
if (event != null)
if(event.getType() == null || Event.TYPE.equals(event.getType())) {
Event e = (Event) event;
+ Instant instant = e.getDateEnd();
+ Instant instant1 = e.getDateStart();
return new EventDTO()
.type(Event.TYPE)
- .dtstart(Transformers.toOffsetDateTime(e.getDateStart()))
- .dtend(Transformers.toOffsetDateTime(e.getDateEnd()))
+ .dtstart(instant1)
+ .dtend(instant)
.rrule(toRecurrenceRuleDTO(e.getRRule()));
} else if(RelativeEvent.TYPE.equals(event.getType())) {
RelativeEvent e = (RelativeEvent) event;
@@ -69,30 +88,34 @@ public static ObservationScheduleDTO toObservationScheduleDTO_V1(ScheduleEvent e
}
private static RecurrenceRule fromRecurrenceRuleDTO(RecurrenceRuleDTO dto) {
- if (dto != null)
+ if (dto != null) {
+ Instant offsetDateTime = dto.getUntil();
return new RecurrenceRule()
.setFreq(dto.getFreq().getValue())
.setInterval(dto.getInterval())
.setCount(dto.getCount())
- .setUntil(Transformers.toInstant(dto.getUntil()))
+ .setUntil(offsetDateTime)
.setByDay(dto.getByday() != null ? dto.getByday().stream().map(WeekdayDTO::getValue).toList() : null)
.setByMonth(dto.getBymonth())
.setByMonthDay(dto.getBymonthday())
.setBySetPos(dto.getBysetpos());
+ }
else return null;
}
private static RecurrenceRuleDTO toRecurrenceRuleDTO(RecurrenceRule recurrenceRule) {
- if (recurrenceRule != null)
+ if (recurrenceRule != null) {
+ Instant instant = recurrenceRule.getUntil();
return new RecurrenceRuleDTO()
.freq(recurrenceRule.getFreq() != null ? FrequencyDTO.fromValue(recurrenceRule.getFreq()) : null)
.interval(recurrenceRule.getInterval())
.count(recurrenceRule.getCount())
- .until(Transformers.toOffsetDateTime(recurrenceRule.getUntil()))
+ .until(instant)
.byday(recurrenceRule.getByDay() != null ? recurrenceRule.getByDay().stream().map(WeekdayDTO::fromValue).toList() : null)
.bymonth(recurrenceRule.getByMonth())
.bymonthday(recurrenceRule.getByMonthDay())
.bysetpos(recurrenceRule.getBySetPos());
+ }
else return null;
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ImportExportTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ImportExportTransformer.java
index c166abfd..a4979f67 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ImportExportTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ImportExportTransformer.java
@@ -14,12 +14,11 @@
import io.redlink.more.studymanager.api.v1.model.StudyImportExportDTO;
import io.redlink.more.studymanager.model.IntegrationInfo;
import io.redlink.more.studymanager.model.StudyImportExport;
-
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
-public class ImportExportTransformer {
+public final class ImportExportTransformer {
private ImportExportTransformer() {}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/InterventionTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/InterventionTransformer.java
index ef1e726f..7595dfb7 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/InterventionTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/InterventionTransformer.java
@@ -10,6 +10,7 @@
import io.redlink.more.studymanager.api.v1.model.InterventionDTO;
import io.redlink.more.studymanager.model.Intervention;
+import java.time.Instant;
public final class InterventionTransformer {
@@ -27,6 +28,8 @@ public static Intervention fromInterventionDTO_V1(InterventionDTO dto) {
}
public static InterventionDTO toInterventionDTO_V1(Intervention intervention) {
+ Instant instant = intervention.getModified();
+ Instant instant1 = intervention.getCreated();
return new InterventionDTO()
.studyId(intervention.getStudyId())
.interventionId(intervention.getInterventionId())
@@ -34,8 +37,8 @@ public static InterventionDTO toInterventionDTO_V1(Intervention intervention) {
.purpose(intervention.getPurpose())
.studyGroupId(intervention.getStudyGroupId())
.schedule(EventTransformer.toObservationScheduleDTO_V1(intervention.getSchedule()))
- .created(Transformers.toOffsetDateTime(intervention.getCreated()))
- .modified(Transformers.toOffsetDateTime(intervention.getModified()));
+ .created(instant1)
+ .modified(instant);
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ObservationTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ObservationTransformer.java
index 077ac44f..56baf62a 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ObservationTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ObservationTransformer.java
@@ -12,6 +12,7 @@
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.model.Observation;
import io.redlink.more.studymanager.utils.MapperUtils;
+import java.time.Instant;
public final class ObservationTransformer {
@@ -34,6 +35,8 @@ public static Observation fromObservationDTO_V1(ObservationDTO dto) {
}
public static ObservationDTO toObservationDTO_V1(Observation observation) {
+ Instant instant = observation.getModified();
+ Instant instant1 = observation.getCreated();
return new ObservationDTO()
.studyId(observation.getStudyId())
.observationId(observation.getObservationId())
@@ -44,8 +47,8 @@ public static ObservationDTO toObservationDTO_V1(Observation observation) {
.studyGroupId(observation.getStudyGroupId())
.properties(observation.getProperties())
.schedule(EventTransformer.toObservationScheduleDTO_V1(observation.getSchedule()))
- .created(Transformers.toOffsetDateTime(observation.getCreated()))
- .modified(Transformers.toOffsetDateTime(observation.getModified()))
+ .created(instant1)
+ .modified(instant)
.hidden(observation.getHidden())
.noSchedule(observation.getNoSchedule());
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ParticipantTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ParticipantTransformer.java
index 098774e0..751940e7 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ParticipantTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ParticipantTransformer.java
@@ -11,8 +11,9 @@
import io.redlink.more.studymanager.api.v1.model.ParticipantDTO;
import io.redlink.more.studymanager.api.v1.model.ParticipantStatusDTO;
import io.redlink.more.studymanager.model.Participant;
+import java.time.Instant;
-public class ParticipantTransformer {
+public final class ParticipantTransformer {
private ParticipantTransformer() {
@@ -27,6 +28,9 @@ public static Participant fromParticipantDTO_V1(ParticipantDTO participantDTO) {
}
public static ParticipantDTO toParticipantDTO_V1(Participant participant) {
+ Instant instant = participant.getCreated();
+ Instant instant1 = participant.getModified();
+ Instant instant2 = participant.getStart();
return new ParticipantDTO()
.studyId(participant.getStudyId())
.participantId(participant.getParticipantId())
@@ -34,9 +38,9 @@ public static ParticipantDTO toParticipantDTO_V1(Participant participant) {
.studyGroupId(participant.getStudyGroupId())
.registrationToken(participant.getRegistrationToken())
.status(ParticipantStatusDTO.fromValue(participant.getStatus().getValue()))
- .start(Transformers.toOffsetDateTime(participant.getStart()))
- .modified(Transformers.toOffsetDateTime(participant.getModified()))
- .created(Transformers.toOffsetDateTime(participant.getCreated()));
+ .start(instant2)
+ .modified(instant1)
+ .created(instant);
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/RoleTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/RoleTransformer.java
index 49e37209..8b25d622 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/RoleTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/RoleTransformer.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model.transformer;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyDataTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyDataTransformer.java
index 2be7363b..e6b654f2 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyDataTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyDataTransformer.java
@@ -9,10 +9,18 @@
package io.redlink.more.studymanager.model.transformer;
import io.redlink.more.studymanager.api.v1.model.IdTitleDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationDataViewDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationDataViewDataDTO;
+import io.redlink.more.studymanager.api.v1.model.ObservationDataViewDataRowDTO;
import io.redlink.more.studymanager.api.v1.model.ParticipationDataDTO;
-import io.redlink.more.studymanager.model.ParticipationData;
+import io.redlink.more.studymanager.core.ui.DataView;
+import io.redlink.more.studymanager.core.ui.DataViewInfo;
+import io.redlink.more.studymanager.core.ui.DataViewRow;
+import io.redlink.more.studymanager.model.data.ParticipationData;
+import java.util.List;
+import java.util.stream.Collectors;
-public class StudyDataTransformer {
+public final class StudyDataTransformer {
private StudyDataTransformer(){}
@@ -23,7 +31,7 @@ public static ParticipationDataDTO toParticipationDataDTO_V1(ParticipationData p
.participantNamedId(toIdTitleDTO_V1(participationData.participantNamedId()))
.studyGroupNamedId(toIdTitleDTO_V1(participationData.studyGroupNamedId()))
.dataReceived(participationData.dataReceived())
- .lastDataReceived(Transformers.toOffsetDateTime(participationData.lastDataReceived()));
+ .lastDataReceived(participationData.lastDataReceived());
}
public static IdTitleDTO toIdTitleDTO_V1(ParticipationData.NamedId idTitle){
if(idTitle == null)
@@ -32,4 +40,40 @@ public static IdTitleDTO toIdTitleDTO_V1(ParticipationData.NamedId idTitle){
.id(idTitle.id())
.title(idTitle.title());
}
+
+ public static ObservationDataViewDataDTO toObservationDataViewDataDTO(DataView dataView){
+ var observationDataViewDataDTO = new ObservationDataViewDataDTO()
+ .view(toObservationDataViewDTO(dataView.viewInfo()))
+ .chartType(toChartTypeEnumDTO(dataView.chartType()));
+
+ if (dataView.data() != null) {
+ observationDataViewDataDTO
+ .labels(dataView.data().labels())
+ .data(toObservationDataViewDataRowDTO(dataView.data().rows()));
+ }
+
+ return observationDataViewDataDTO;
+ }
+
+ public static ObservationDataViewDTO toObservationDataViewDTO(DataViewInfo dataViewInfo) {
+ return new ObservationDataViewDTO()
+ .name(dataViewInfo.name())
+ .label(dataViewInfo.label())
+ .title(dataViewInfo.title())
+ .description(dataViewInfo.description());
+ }
+
+ private static ObservationDataViewDataDTO.ChartTypeEnum toChartTypeEnumDTO(DataView.ChartType chartType) {
+ return switch (chartType) {
+ case LINE -> ObservationDataViewDataDTO.ChartTypeEnum.LINE;
+ case BAR -> ObservationDataViewDataDTO.ChartTypeEnum.BAR;
+ case PIE -> ObservationDataViewDataDTO.ChartTypeEnum.PIE;
+ };
+ }
+
+ private static List toObservationDataViewDataRowDTO(List dataViewRow) {
+ return dataViewRow.stream()
+ .map(row -> new ObservationDataViewDataRowDTO().label(row.label()).values(row.values()))
+ .collect(Collectors.toList());
+ }
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyGroupTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyGroupTransformer.java
index 7ad8a436..0ab946d4 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyGroupTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyGroupTransformer.java
@@ -11,6 +11,7 @@
import io.redlink.more.studymanager.api.v1.model.StudyGroupDTO;
import io.redlink.more.studymanager.model.StudyGroup;
import io.redlink.more.studymanager.model.scheduler.Duration;
+import java.time.Instant;
public final class StudyGroupTransformer {
@@ -27,13 +28,15 @@ public static StudyGroup fromStudyGroupDTO_V1(StudyGroupDTO studyGroupDTO) {
}
public static StudyGroupDTO toStudyGroupDTO_V1(StudyGroup studyGroup) {
+ Instant instant = studyGroup.getModified();
+ Instant instant1 = studyGroup.getCreated();
return new StudyGroupDTO()
.studyId(studyGroup.getStudyId())
.studyGroupId(studyGroup.getStudyGroupId())
.title(studyGroup.getTitle())
.purpose(studyGroup.getPurpose())
.duration(Duration.toStudyDurationDTO(studyGroup.getDuration()))
- .created(Transformers.toOffsetDateTime(studyGroup.getCreated()))
- .modified(Transformers.toOffsetDateTime(studyGroup.getModified()));
+ .created(instant1)
+ .modified(instant);
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java
index 24ac7edd..6afb87bf 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/StudyTransformer.java
@@ -15,8 +15,9 @@
import io.redlink.more.studymanager.model.Contact;
import io.redlink.more.studymanager.model.Study;
import io.redlink.more.studymanager.model.scheduler.Duration;
+import java.time.Instant;
-public class StudyTransformer {
+public final class StudyTransformer {
private StudyTransformer() {}
@@ -41,6 +42,8 @@ public static Study fromStudyDTO_V1(StudyDTO studyDTO) {
public static StudyDTO toStudyDTO_V1(Study study) {
if(study.getContact() == null)
study.setContact(new Contact());
+ Instant instant = study.getModified();
+ Instant instant1 = study.getCreated();
return new StudyDTO()
.studyId(study.getStudyId())
.title(study.getTitle())
@@ -54,8 +57,8 @@ public static StudyDTO toStudyDTO_V1(Study study) {
.end(study.getEndDate())
.plannedStart(study.getPlannedStartDate())
.plannedEnd(study.getPlannedEndDate())
- .created(Transformers.toOffsetDateTime(study.getCreated()))
- .modified(Transformers.toOffsetDateTime(study.getModified()))
+ .created(instant1)
+ .modified(instant)
.userRoles(RoleTransformer.toStudyRolesDTO(study.getUserRoles()))
.contact(ContactTransformer.toContactDTO_V1(study.getContact()));
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TimelineTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TimelineTransformer.java
index 6f62aef1..9aa090d5 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TimelineTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TimelineTransformer.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.model.transformer;
@@ -10,12 +18,12 @@
import io.redlink.more.studymanager.model.timeline.StudyTimeline;
-public class TimelineTransformer {
+public final class TimelineTransformer {
private TimelineTransformer() {}
public static StudyTimelineDTO toStudyTimelineDTO(StudyTimeline studyTimeline) {
return new StudyTimelineDTO()
- .participantSignup(Transformers.toOffsetDateTime(studyTimeline.signup()))
+ .participantSignup(studyTimeline.signup())
.studyDuration(
new StudyTimelineStudyDurationDTO()
.from(studyTimeline.participationRange().getMinimum())
@@ -36,8 +44,8 @@ public static ObservationTimelineEventDTO toObservationTimelineDTO(ObservationTi
.title(observationTimelineEvent.title())
.purpose(observationTimelineEvent.purpose())
.type(observationTimelineEvent.type())
- .start(Transformers.toOffsetDateTime(observationTimelineEvent.start()))
- .end(Transformers.toOffsetDateTime(observationTimelineEvent.end()))
+ .start(observationTimelineEvent.start())
+ .end(observationTimelineEvent.end())
.hidden(observationTimelineEvent.hidden())
.scheduleType(observationTimelineEvent.scheduleType());
}
@@ -48,7 +56,7 @@ public static InterventionTimelineEventDTO toInterventionTimelineEventDTO(Interv
.studyGroupId(interventionTimelineEvent.studyGroupId())
.title(interventionTimelineEvent.title())
.purpose(interventionTimelineEvent.purpose())
- .start(Transformers.toOffsetDateTime(interventionTimelineEvent.start()))
+ .start(interventionTimelineEvent.start())
.scheduleType(interventionTimelineEvent.scheduleType());
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java
index 6e39e833..454b2e92 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/Transformers.java
@@ -1,11 +1,13 @@
/*
- * Copyright (c) 2023 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model.transformer;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
import java.util.function.Function;
/**
@@ -13,19 +15,9 @@
*/
public final class Transformers {
- private static final ZoneId HOME = ZoneId.of("Europe/Vienna");
-
private Transformers() {
}
- public static OffsetDateTime toOffsetDateTime(Instant instant) {
- return transform(instant, i -> i.atZone(HOME).toOffsetDateTime());
- }
-
- public static Instant toInstant(OffsetDateTime offsetDateTime) {
- return transform(offsetDateTime, OffsetDateTime::toInstant);
- }
-
/**
* Performs a null-safe conversion of {@code t} using the {@code transformer}.
* @param t the value to transform.
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TriggerTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TriggerTransformer.java
index df6e7e22..9e0f771c 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TriggerTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/TriggerTransformer.java
@@ -12,6 +12,7 @@
import io.redlink.more.studymanager.core.properties.TriggerProperties;
import io.redlink.more.studymanager.model.Trigger;
import io.redlink.more.studymanager.utils.MapperUtils;
+import java.time.Instant;
public final class TriggerTransformer {
@@ -25,10 +26,12 @@ public static Trigger fromTriggerDTO_V1(TriggerDTO dto) {
}
public static TriggerDTO toTriggerDTO_V1(Trigger trigger) {
+ Instant instant = trigger.getModified();
+ Instant instant1 = trigger.getCreated();
return new TriggerDTO()
.type(trigger.getType())
.properties(trigger.getProperties())
- .created(Transformers.toOffsetDateTime(trigger.getCreated()))
- .modified(Transformers.toOffsetDateTime(trigger.getModified()));
+ .created(instant1)
+ .modified(instant);
}
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/UserInfoTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/UserInfoTransformer.java
index e3016c9a..a177885b 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/UserInfoTransformer.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/UserInfoTransformer.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.model.transformer;
@@ -76,7 +81,7 @@ private static CollaboratorRoleDetailsDTO toCollaboratorRoleDetailsDTO(StudyUser
return new CollaboratorRoleDetailsDTO()
.role(RoleTransformer.toStudyRoleDTO(role.role()))
.assignedBy(toUserInfoDTO(role.creator()))
- .assignedAt(Transformers.toOffsetDateTime(role.created()))
+ .assignedAt(role.created())
;
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ValidationReportTransformer.java b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ValidationReportTransformer.java
new file mode 100644
index 00000000..c9e25d3e
--- /dev/null
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/model/transformer/ValidationReportTransformer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.model.transformer;
+
+import io.redlink.more.studymanager.api.v1.model.ValidationReportDTO;
+import io.redlink.more.studymanager.api.v1.model.ValidationReportItemDTO;
+import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
+import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport;
+import io.redlink.more.studymanager.core.validation.ValidationIssue;
+import java.util.List;
+
+public final class ValidationReportTransformer {
+
+ private ValidationReportTransformer() {
+ }
+
+ public static ValidationReportDTO validationReportDTO_V1(ConfigurationValidationException validation) {
+ return validationReportDTO_V1(validation.getReport())
+ .valid(false);
+ }
+
+ public static ValidationReportDTO validationReportDTO_V1(ConfigurationValidationReport report) {
+ return new ValidationReportDTO()
+ .valid(report.isValid())
+ .errors(validationReportItemDTO_V1(report.getErrors(), "error"))
+ .warnings(validationReportItemDTO_V1(report.getWarnings(), "warning"));
+ }
+
+ private static List validationReportItemDTO_V1(List issues, String type) {
+ return issues.stream()
+ .map(i -> validationReportItemDTO_V1(i, type))
+ .toList();
+ }
+
+ private static ValidationReportItemDTO validationReportItemDTO_V1(ValidationIssue issue, String type) {
+ return new ValidationReportItemDTO()
+ .message(issue.getMessage())
+ .propertyId(issue.getPropertyId())
+ .componentTitle(issue.getComponentTitle())
+ .type(type);
+ }
+
+}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/properties/GatewayProperties.java b/studymanager/src/main/java/io/redlink/more/studymanager/properties/GatewayProperties.java
index 6dc942c2..b29d8f3f 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/properties/GatewayProperties.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/properties/GatewayProperties.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java
index 5c6154d3..a6766333 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/RepositoryUtils.java
@@ -1,16 +1,20 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.repository;
import io.redlink.more.studymanager.model.Participant;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;
public final class RepositoryUtils {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyAclRepository.java b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyAclRepository.java
index 749adad4..e5d168e2 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyAclRepository.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/repository/StudyAclRepository.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.repository;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/sdk/MoreSDK.java b/studymanager/src/main/java/io/redlink/more/studymanager/sdk/MoreSDK.java
index 9d532532..95cebadc 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/sdk/MoreSDK.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/sdk/MoreSDK.java
@@ -15,6 +15,8 @@
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
import io.redlink.more.studymanager.core.sdk.MoreTriggerSDK;
import io.redlink.more.studymanager.core.sdk.schedule.Schedule;
+import io.redlink.more.studymanager.core.ui.DataViewData;
+import io.redlink.more.studymanager.core.ui.ViewConfig;
import io.redlink.more.studymanager.model.Participant;
import io.redlink.more.studymanager.model.data.ElasticActionDataPoint;
import io.redlink.more.studymanager.model.data.ElasticDataPoint;
@@ -26,18 +28,23 @@
import io.redlink.more.studymanager.sdk.scoped.MoreActionSDKImpl;
import io.redlink.more.studymanager.sdk.scoped.MoreObservationSDKImpl;
import io.redlink.more.studymanager.sdk.scoped.MoreTriggerSDKImpl;
+import io.redlink.more.studymanager.service.ElasticDataService;
import io.redlink.more.studymanager.service.ElasticService;
import io.redlink.more.studymanager.service.ParticipantService;
import io.redlink.more.studymanager.service.PushNotificationService;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
-import java.io.Serializable;
-import java.time.Instant;
-import java.util.*;
-import java.util.stream.Collectors;
-
@Component
public class MoreSDK {
@@ -51,6 +58,8 @@ public class MoreSDK {
private final ElasticService elasticService;
+ private final ElasticDataService elasticDataService;
+
private final PushNotificationService pushNotificationService;
private final ObservationRepository observationRepository;
@@ -59,12 +68,13 @@ public MoreSDK(
NameValuePairRepository nvpairs,
SchedulingService schedulingService,
ParticipantService participantService,
- ElasticService elasticService,
+ ElasticService elasticService, ElasticDataService elasticDataService,
PushNotificationService pushNotificationService, ObservationRepository observationRepository) {
this.nvpairs = nvpairs;
this.schedulingService = schedulingService;
this.participantService = participantService;
this.elasticService = elasticService;
+ this.elasticDataService = elasticDataService;
this.pushNotificationService = pushNotificationService;
this.observationRepository = observationRepository;
}
@@ -169,4 +179,13 @@ public Optional getPropertiesForParticipant(long studyId,
public void removePropertiesForParticipant(long studyId, Integer participantId, int observationId) {
observationRepository.removeParticipantProperties(studyId, participantId, observationId);
}
+
+ public DataViewData queryData(ViewConfig viewConfig, long studyId, Integer studyGroupId, int observationId, Integer participantId, TimeRange timerange) {
+ try {
+ return elasticDataService.queryObservationViewData(viewConfig, studyId, studyGroupId, observationId, participantId, timerange);
+ } catch (IOException e) {
+ LOGGER.warn("Failed to query observation data for view config {}", viewConfig, e);
+ return null;
+ }
+ }
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/sdk/scoped/MoreObservationSDKImpl.java b/studymanager/src/main/java/io/redlink/more/studymanager/sdk/scoped/MoreObservationSDKImpl.java
index d984ba47..82a8636c 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/sdk/scoped/MoreObservationSDKImpl.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/sdk/scoped/MoreObservationSDKImpl.java
@@ -8,13 +8,18 @@
*/
package io.redlink.more.studymanager.sdk.scoped;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
import io.redlink.more.studymanager.core.sdk.MoreObservationSDK;
+import io.redlink.more.studymanager.core.ui.DataViewData;
+import io.redlink.more.studymanager.core.ui.DataViewRow;
+import io.redlink.more.studymanager.core.ui.ViewConfig;
import io.redlink.more.studymanager.model.data.ElasticDataPoint;
import io.redlink.more.studymanager.sdk.MoreSDK;
import java.io.Serializable;
import java.time.Instant;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -79,4 +84,9 @@ public void removePropertiesForParticipant(Integer participantId) {
public void storeDataPoint(Integer participantId, String observationType, Map data) {
sdk.storeDatapoint(ElasticDataPoint.Type.observation, studyId, studyGroupId, participantId, observationId, observationType, Instant.now(), data);
}
+
+ @Override
+ public DataViewData queryData(ViewConfig viewConfig, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ return sdk.queryData(viewConfig, studyId, studyGroupId, observationId, participantId, timerange);
+ }
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/CalendarService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/CalendarService.java
index 8c2c48b1..e621261a 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/CalendarService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/CalendarService.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.service;
import io.redlink.more.studymanager.exception.NotFoundException;
@@ -14,7 +22,6 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
-import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.List;
@@ -41,7 +48,7 @@ public CalendarService(StudyService studyService, ObservationService observation
this.participantService = participantService;
}
- public StudyTimeline getTimeline(Long studyId, Integer participantId, Integer studyGroupId, OffsetDateTime referenceDate, LocalDate from, LocalDate to) {
+ public StudyTimeline getTimeline(Long studyId, Integer participantId, Integer studyGroupId, Instant referenceDate, LocalDate from, LocalDate to) {
final Study study = studyService.getStudy(studyId, null)
.orElseThrow(() -> NotFoundException.Study(studyId));
final Range studyRange = Range.of(
@@ -66,7 +73,7 @@ public StudyTimeline getTimeline(Long studyId, Integer participantId, Integer st
*/
final Instant participantStart;
if (referenceDate != null) {
- participantStart = referenceDate.toInstant();
+ participantStart = referenceDate;
} else if (participant != null && participant.getStart() != null) {
participantStart = participant.getStart();
} else {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/DataProcessingService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/DataProcessingService.java
index 26e5f415..1645ed84 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/DataProcessingService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/DataProcessingService.java
@@ -9,17 +9,24 @@
package io.redlink.more.studymanager.service;
import io.redlink.more.studymanager.api.v1.model.DataPointDTO;
+import io.redlink.more.studymanager.core.io.Timeframe;
+import io.redlink.more.studymanager.core.ui.DataView;
+import io.redlink.more.studymanager.core.ui.DataViewInfo;
import io.redlink.more.studymanager.model.Observation;
import io.redlink.more.studymanager.model.Participant;
-import io.redlink.more.studymanager.model.ParticipationData;
import io.redlink.more.studymanager.model.StudyGroup;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.stereotype.Service;
-
+import io.redlink.more.studymanager.model.data.ParticipationData;
import java.io.IOException;
-import java.time.OffsetDateTime;
+import java.time.Instant;
import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
@Service
public class DataProcessingService {
@@ -112,7 +119,20 @@ public List getParticipationData(Long studyId){
return participationDataList;
}
- public List getDataPoints(Long studyId, Integer size, Integer observationId, Integer participantId, OffsetDateTime date) {
+ public DataViewInfo[] listDataViews(Long studyId, Integer observationId) {
+ return observationService.listDataViews(studyId, observationId);
+ }
+
+ public DataView getDataView(Long studyId, Integer observationId, String viewName, Integer studyGroupId, Integer participantId, Instant from, Instant to) {
+ if (participantId != null) {
+ return observationService.queryData(studyId, observationId, viewName, null, participantId, from != null && to != null ? new Timeframe(from, to) : null);
+ } else if (studyGroupId != null) {
+ return observationService.queryData(studyId, observationId, viewName, studyGroupId, null, from != null && to != null ? new Timeframe(from, to) : null);
+ }
+ return observationService.queryData(studyId, observationId, viewName, studyGroupId, participantId, from != null && to != null ? new Timeframe(from, to) : null);
+ }
+
+ public List getDataPoints(Long studyId, Integer size, Integer observationId, Integer participantId, Instant date) {
try {
return elasticService.listDataPoints(studyId, participantId, observationId, toIsoString(date), size).stream()
.map(dp -> new DataPointDTO()
@@ -129,8 +149,8 @@ public List getDataPoints(Long studyId, Integer size, Integer obse
}
}
- private String toIsoString(OffsetDateTime date) {
- return date != null ? date.format(DateTimeFormatter.ISO_INSTANT) : null;
+ private String toIsoString(Instant date) {
+ return date != null ? DateTimeFormatter.ISO_INSTANT.format(date) : null;
}
@Cacheable("participants")
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticDataService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticDataService.java
new file mode 100644
index 00000000..1818deef
--- /dev/null
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticDataService.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
+package io.redlink.more.studymanager.service;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.ElasticsearchException;
+import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
+import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
+import co.elastic.clients.elasticsearch._types.aggregations.DateHistogramBucket;
+import co.elastic.clients.elasticsearch._types.aggregations.MinimumInterval;
+import co.elastic.clients.elasticsearch._types.aggregations.MultiBucketBase;
+import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
+import co.elastic.clients.util.ObjectBuilder;
+import io.redlink.more.studymanager.core.io.TimeRange;
+import io.redlink.more.studymanager.core.ui.DataViewData;
+import io.redlink.more.studymanager.core.ui.DataViewRow;
+import io.redlink.more.studymanager.core.ui.ViewConfig;
+import io.redlink.more.studymanager.model.StudyGroup;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import static io.redlink.more.studymanager.service.ElasticService.getFilters;
+import static io.redlink.more.studymanager.service.ElasticService.getStudyIdString;
+
+@Service
+public class ElasticDataService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ElasticDataService.class);
+
+ private static final String AGG_NAME_SERIES = "series";
+ private static final String AGG_NAME_ROWS = "rows";
+ private static final String AGG_NAME_VALUES = "values";
+ private static final String NO_GROUP_KEY = "no_group";
+
+ private final ElasticsearchClient client;
+
+ private final ParticipantService participantService;
+ private final StudyGroupService studyGroupService;
+
+ public ElasticDataService(ElasticsearchClient client, ParticipantService participantService, StudyGroupService studyGroupService) {
+ this.client = client;
+ this.participantService = participantService;
+ this.studyGroupService = studyGroupService;
+ }
+
+ public DataViewData queryObservationViewData(ViewConfig viewConfig, long studyId, Integer studyGroupId, int observationId, Integer participantId, TimeRange timerange) throws IOException {
+ final List filters = getFilters(studyId, observationId, studyGroupId, participantId, timerange);
+
+ final SearchRequest.Builder builder = buildDataPreviewRequest(viewConfig, filters, studyId);
+ final SearchRequest request = builder.build();
+
+ try {
+ final SearchResponse searchResponse = client.search(request, Void.class);
+ return processDataPreviewResponse(viewConfig, searchResponse, studyId);
+ } catch (IOException | ElasticsearchException e) {
+ return ElasticService.handleIndexNotFoundException(e, () -> null, IOException::new);
+ }
+ }
+
+ private SearchRequest.Builder buildDataPreviewRequest(ViewConfig viewConfig, List filters, long studyId) {
+ final var rows = viewConfig.rowAggregation();
+ final var series = viewConfig.seriesAggregation();
+ return new SearchRequest.Builder()
+ .index(getStudyIdString(studyId))
+ .size(0)
+ .query(q -> q.bool(b -> b.filter(filters)))
+ .aggregations(AGG_NAME_SERIES, s ->
+ applyAggregation(s, series, viewConfig.operation())
+ .aggregations(AGG_NAME_ROWS, r ->
+ applyAggregation(r, rows, viewConfig.operation())
+ .aggregations(AGG_NAME_VALUES, d -> applyOperation(d, viewConfig))
+ )
+
+ )
+ .aggregations("rowLabels", rl ->
+ applyAggregation(rl, rows, viewConfig.operation())
+ )
+ ;
+ }
+
+ private List extends MultiBucketBase> readBuckets(Map aggregations, String aggName) {
+ final var agg = aggregations.get(aggName);
+ if (agg.isAutoDateHistogram()) {
+ return agg.autoDateHistogram()
+ .buckets().array();
+ } else if (agg.isSterms()) {
+ return agg.sterms()
+ .buckets().array();
+ }
+ throw new IllegalStateException("Unknown aggregation type: " + agg._kind());
+ }
+
+ private String readBucketKey(MultiBucketBase bucket) {
+ if (bucket instanceof StringTermsBucket str) {
+ return str.key().stringValue();
+ } else if (bucket instanceof DateHistogramBucket date) {
+ return date.keyAsString();
+ } else {
+ return null;
+ }
+ }
+
+ private Function createTitleResolver(ViewConfig.Aggregation aggregation, long studyId) {
+ if (aggregation == null) {
+ return Function.identity();
+ }
+
+ final Map mapping = switch (aggregation) {
+ case PARTICIPANT ->
+ participantService.listParticipants(studyId)
+ .stream()
+ .collect(Collectors.toMap(
+ p -> ElasticService.getParticipantIdString(p.getParticipantId()),
+ p -> String.format("%s (%d)", p.getAlias(), p.getParticipantId())
+ ));
+
+ case STUDY_GROUP -> {
+ final Map m = new HashMap<>(
+ studyGroupService.listStudyGroups(studyId)
+ .stream()
+ .collect(Collectors.toMap(
+ g -> ElasticService.getStudyGroupIdString(g.getStudyGroupId()),
+ StudyGroup::getTitle
+ ))
+ );
+ m.put(NO_GROUP_KEY, "i18n.global.placeholder.noGroup");
+ yield m;
+ }
+ default -> Map.of();
+ };
+ return l -> mapping.getOrDefault(l, l);
+ }
+
+ private DataViewData processDataPreviewResponse(ViewConfig viewConfig, SearchResponse searchResponse, long studyId) {
+
+ final List extends MultiBucketBase> seriesBuckets = readBuckets(searchResponse.aggregations(), AGG_NAME_SERIES);
+ final int seriesCount = seriesBuckets.size();
+ final List labels = new ArrayList<>(seriesCount);
+ final Supplier> createValueArray = () -> {
+ final ArrayList array = new ArrayList<>(seriesCount);
+ for (int i = 0; i < seriesCount; i++) {
+ array.add(null);
+ }
+ return array;
+ };
+
+ final LinkedHashMap> rowMap = new LinkedHashMap<>();
+
+ final Function labelsTitleResolver = createTitleResolver(viewConfig.seriesAggregation(), studyId);
+ for (MultiBucketBase bucket : seriesBuckets) {
+ final String bucketKey = readBucketKey(bucket);
+
+ labels.add(labelsTitleResolver.apply(bucketKey));
+ final int seriesIdx = labels.size() - 1;
+
+ final List extends MultiBucketBase> rowsBuckets = readBuckets(bucket.aggregations(), AGG_NAME_ROWS);
+ for (MultiBucketBase rowBucket : rowsBuckets) {
+ final String rowKey = readBucketKey(rowBucket);
+
+ final var valueAgg = rowBucket.aggregations().get(AGG_NAME_VALUES);
+ final var value = switch (viewConfig.operation().operator()) {
+ case SUM, COUNT -> valueAgg.sum().value();
+ case MIN -> valueAgg.min().value();
+ case MAX -> valueAgg.max().value();
+ case AVG -> valueAgg.avg().value();
+ };
+ rowMap.computeIfAbsent(rowKey, k -> createValueArray.get())
+ .set(seriesIdx, value);
+ }
+ }
+
+ final Function rowTitleResolver = createTitleResolver(viewConfig.rowAggregation(), studyId);
+ return new DataViewData(
+ List.copyOf(labels),
+ rowMap.entrySet().stream()
+ .map(e -> new DataViewRow(
+ rowTitleResolver.apply(e.getKey()),
+ e.getValue()
+ ))
+ .toList()
+ );
+ }
+
+ private Aggregation.Builder.ContainerBuilder applyAggregation(Aggregation.Builder a, ViewConfig.Aggregation aggregation, ViewConfig.Operation operation) {
+ if (aggregation == null) {
+ // If there's no aggregation required at this level, we perform
+ // "no-op"-aggregation to keep response-structure aligned.
+ return a.terms(n -> n.field("study_id.keyword"));
+ }
+ return switch (aggregation) {
+ case TIME -> a.autoDateHistogram(dateHistogram -> dateHistogram
+ .field("effective_time_frame")
+ .buckets(1000) // TODO: Hidden magic number, aligned with chart-width in the UI!
+ .minimumInterval(MinimumInterval.Minute)
+ .format("yyyy-MM-dd'T'HH:mmZ")
+ );
+ case PARTICIPANT -> a.terms(pt -> pt.field("participant_id.keyword"));
+ case STUDY_GROUP -> a.terms(sg -> sg.field("study_group_id.keyword")
+ .minDocCount(0)
+ .missing(NO_GROUP_KEY)
+ );
+ case TERM_FIELD -> a.terms(tf -> tf.field("data_%s.keyword".formatted(operation.field()))
+ .minDocCount(1)
+ );
+ };
+ }
+
+ private ObjectBuilder applyOperation(Aggregation.Builder agg, ViewConfig viewConfig) {
+ final String field = viewConfig.operation().field();
+ return switch (viewConfig.operation().operator()) {
+ case AVG -> agg.avg(
+ s -> s.field(String.format("data_%s", field))
+ );
+ case SUM -> agg.sum(
+ s -> s.field(String.format("data_%s", field))
+ );
+ case MIN -> agg.min(
+ m -> m.field(String.format("data_%s", field))
+ );
+ case MAX -> agg.max(
+ m -> m.field(String.format("data_%s", field))
+ );
+ case COUNT -> agg.sum(
+ s -> s.field("non_existing_field")
+ .missing(1)
+ );
+ };
+ }
+
+
+}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java
index 8035caa6..a83e5b62 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ElasticService.java
@@ -14,24 +14,26 @@
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
-import co.elastic.clients.elasticsearch.core.*;
+import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
+import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.CloseIndexRequest;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
+import co.elastic.clients.json.JsonData;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Iterables;
import io.redlink.more.studymanager.core.io.TimeRange;
-import io.redlink.more.studymanager.model.ParticipationData;
import io.redlink.more.studymanager.model.Study;
import io.redlink.more.studymanager.model.data.ElasticDataPoint;
+import io.redlink.more.studymanager.model.data.ParticipationData;
import io.redlink.more.studymanager.model.data.SimpleDataPoint;
import io.redlink.more.studymanager.properties.ElasticProperties;
import io.redlink.more.studymanager.utils.MapperUtils;
-import java.util.Objects;
-import java.util.function.Function;
-import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -41,10 +43,9 @@
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
@Service
@@ -95,29 +96,73 @@ public List participantsThatMapQuery(Long studyId, Integer studyGroupId
}
}
- private List getFilters(Long studyId, Integer studyGroupId, TimeRange timerange) {
+ static List getFilters(Long studyId, Integer studyGroupId, TimeRange timerange) {
List queries = new ArrayList<>();
- queries.add(Query.of(f -> f.
- term(t -> t.
- field("study_id").
- value(getStudyIdString(studyId)))));
+ queries.add(getStudyFilter(studyId));
if (studyGroupId != null) {
- queries.add(Query.of(f -> f.term(t -> t.
- field("study_group_id").
- value(getStudyGroupIdString(studyGroupId)))));
+ queries.add(getStudyGroupFilter(studyGroupId));
}
if (timerange != null && timerange.getFromString() != null && timerange.getToString() != null) {
- queries.add(Query.of(f -> f.
- range(r -> r.
- field("effective_time_frame").
- from(timerange.getFromString()).
- to(timerange.getToString())
- )));
+ queries.add(getTimeRangeFilter(timerange));
}
return queries;
}
+ static List getFilters(Long studyId, Integer observationId, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ List filters = new ArrayList<>();
+ filters.add(getStudyFilter(studyId));
+ filters.add(getObservationFilter(observationId));
+
+ if (participantId != null) {
+ filters.add(getParticipantFilter(participantId));
+ } else if(studyGroupId != null) {
+ filters.add(getStudyGroupFilter(studyGroupId));
+ }
+
+ if (timerange != null && timerange.getFromString() != null && timerange.getToString() != null) {
+ filters.add(getTimeRangeFilter(timerange));
+ }
+ return filters;
+ }
+
+ static Query getStudyFilter(Long studyId) {
+ return Query.of(f -> f.
+ term(t -> t.
+ field("study_id.keyword").
+ value(getStudyIdString(studyId))));
+ }
+
+ static Query getObservationFilter(Integer observationId) {
+ return Query.of(f -> f.
+ term(t -> t.
+ field("observation_id.keyword").
+ value(observationId)));
+ }
+
+ static Query getStudyGroupFilter(Integer studyGroupId) {
+ return Query.of(f -> f.
+ term(t -> t.
+ field("study_group_id.keyword").
+ value(getStudyGroupIdString(studyGroupId))));
+ }
+
+ static Query getParticipantFilter(Integer participantId) {
+ return Query.of(f -> f.
+ term(t -> t.
+ field("participant_id.keyword").
+ value(getParticipantIdString(participantId))));
+ }
+
+ static Query getTimeRangeFilter(TimeRange timerange) {
+ return Query.of(f -> f.
+ range(r -> r.
+ field("effective_time_frame").
+ from(timerange.getFromString()).
+ to(timerange.getToString())
+ ));
+ }
+
public boolean indexExists(long studyId) {
ExistsRequest request = new ExistsRequest.Builder()
.index(getStudyIdString(studyId))
@@ -185,7 +230,7 @@ static String getStudyIdString(Study study) {
return getStudyIdString(study.getStudyId());
}
- private String getStudyGroupIdString(Integer studyGroupId) {
+ static String getStudyGroupIdString(Integer studyGroupId) {
return "study_group_" + studyGroupId;
}
@@ -193,6 +238,10 @@ static String getStudyIdString(Long id) {
return "study_" + id;
}
+ static String getParticipantIdString(Integer participantId) {
+ return "participant_" + participantId;
+ }
+
public void setDataPoint(Long studyId, ElasticDataPoint elasticActionDataPoint) {
try {
client.index(i -> i.index(getStudyIdString(studyId)).document(elasticActionDataPoint));
@@ -279,23 +328,23 @@ public List getParticipationData(Long studyId){
}
}
- public void exportData(Long studyId, OutputStream outputStream) throws IOException {
+ public void exportData(OutputStream outputStream, Long studyId, List studyGroupId, List participantId, List observationId, Instant from, Instant to) throws IOException {
String index = getStudyIdString(studyId);
if(!client.indices().exists(e -> e.index(index)).value()) {
return;
}
- SearchRequest request = getQuery(index, null);
+ SearchRequest request = getQuery(index, studyGroupId, participantId, observationId, from, to, null);
SearchResponse rsp = client.search(request, JsonNode.class);
while (rsp.hits().hits().size() > 0) {
writeHits(rsp.hits().hits(), outputStream);
outputStream.flush();
List searchAfterSort = Iterables.getLast(rsp.hits().hits()).sort();
- request = getQuery(index, searchAfterSort);
+ request = getQuery(index, studyGroupId, participantId, observationId, from, to, searchAfterSort);
rsp = client.search(request, JsonNode.class);
- if(rsp.hits().hits().size() > 0) {
+ if (rsp.hits().hits().size() > 0) {
outputStream.write(",".getBytes(StandardCharsets.UTF_8));
}
}
@@ -309,21 +358,46 @@ private void writeHits(List> hits, OutputStream outputStream) thro
outputStream.write(datapoints.getBytes(StandardCharsets.UTF_8));
}
- private SearchRequest getQuery(String index, List searchAfterSort) {
+ private SearchRequest getQuery(String index, List studyGroupIds, List participantIds, List observationIds, Instant from, Instant to, List searchAfterSort) {
SearchRequest.Builder builder = new SearchRequest.Builder();
+ BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
+
+ if (studyGroupIds != null && !studyGroupIds.isEmpty()) {
+ List studyGroupIdsStrings = studyGroupIds.stream()
+ .map(ElasticService::getStudyGroupIdString)
+ .toList();
+ boolQueryBuilder.filter(f -> f.terms(t -> t.field("study_group_id").terms(TermsQueryField.of(tf -> tf.value(studyGroupIdsStrings.stream().map(FieldValue::of).collect(Collectors.toList()))))));
+ }
+
+ if (participantIds != null && !participantIds.isEmpty()) {
+ List participantIdStrings = participantIds.stream()
+ .map(ElasticService::getParticipantIdString)
+ .toList();
+ boolQueryBuilder.filter(f -> f.terms(t -> t.field("participant_id").terms(TermsQueryField.of(tf -> tf.value(participantIdStrings.stream().map(FieldValue::of).collect(Collectors.toList()))))));
+ }
+
+ if (observationIds != null && !observationIds.isEmpty()) {
+ boolQueryBuilder.filter(f -> f.terms(t -> t.field("observation_id").terms(v -> v.value(observationIds.stream().map(FieldValue::of).collect(Collectors.toList())))));
+ }
+
+ if (from != null && to != null) {
+ boolQueryBuilder.filter(f -> f.range(r -> r.field("effective_time_frame").gte(JsonData.of(from)).lte(JsonData.of(to))));
+ }
+
builder.index(index)
- .query(q -> q.matchAll(m -> m))
- //.pit(p -> p.id(pitId).keepAlive(k -> k.time("1m")))
+ .query(q -> q.bool(boolQueryBuilder.build()))
.sort(s -> s.field(f -> f.field("effective_time_frame").order(SortOrder.Asc)))
+ .sort(s -> s.field(f -> f.field("datapoint_id.keyword").order(SortOrder.Asc)))
.size(BATCH_SIZE_FOR_EXPORT_REQUESTS);
- if(searchAfterSort != null) {
+ if (searchAfterSort != null) {
builder.searchAfter(searchAfterSort);
}
return builder.build();
}
+
public List listDataPoints(
Long studyId, Integer participantId, Integer observationId, String isoDate, int size) throws IOException {
SearchRequest.Builder builder = new SearchRequest.Builder();
@@ -372,16 +446,16 @@ private Map toData(Map source) {
return result;
}
- private static R handleIndexNotFoundException(T e, Supplier supplier) throws T {
+ static R handleIndexNotFoundException(T e, Supplier supplier) throws T {
return ElasticService.handleIndexNotFoundException(e, supplier, Function.identity());
}
- private static R handleIndexNotFoundException(E e, Supplier supplier, Function exceptionTFunction) throws T {
+ static R handleIndexNotFoundException(E e, Supplier supplier, Function exceptionTFunction) throws T {
if (isElasticIndexNotFound(e)) return supplier.get();
throw exceptionTFunction.apply(e);
}
- private static boolean isElasticIndexNotFound(Exception e) {
+ static boolean isElasticIndexNotFound(Exception e) {
if (e instanceof ElasticsearchException ee) {
if (Objects.equals(ee.error().type(), "index_not_found_exception")) {
LOG.debug("Swallowing Index-Not-Found from Elastic");
@@ -391,11 +465,11 @@ private static boolean isElasticIndexNotFound(Exception e) {
return false;
}
- private static void handleIndexNotFoundException(E e) throws E {
+ static void handleIndexNotFoundException(E e) throws E {
handleIndexNotFoundException(e, Function.identity());
}
- private static void handleIndexNotFoundException(E e, Function exceptionWrapper) throws T {
+ static void handleIndexNotFoundException(E e, Function exceptionWrapper) throws T {
if (!isElasticIndexNotFound(e))
throw exceptionWrapper.apply(e);
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ImportExportService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ImportExportService.java
index a4273d75..d4889342 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ImportExportService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ImportExportService.java
@@ -10,7 +10,6 @@
import io.redlink.more.studymanager.exception.NotFoundException;
import io.redlink.more.studymanager.model.*;
-import jakarta.servlet.ServletOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,7 +21,9 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.*;
@Service
@@ -151,19 +152,19 @@ public Study importStudy(StudyImportExport studyImport, AuthenticatedUser user)
return newStudy;
}
- public void exportStudyData(ServletOutputStream outputStream, Long studyId) {
+ public void exportStudyData(OutputStream outputStream, Long studyId, List studyGroupId, List participantId, List observationId, Instant from, Instant to) {
if (studyService.existsStudy(studyId).orElse(false)) {
- exportStudyDataAsync(outputStream, studyId);
+ exportStudyDataAsync(outputStream, studyId, studyGroupId, participantId, observationId, from, to);
} else {
throw NotFoundException.Study(studyId);
}
}
@Async
- public void exportStudyDataAsync(ServletOutputStream outputStream, Long studyId) {
+ public void exportStudyDataAsync(OutputStream outputStream, Long studyId, List studyGroupId, List participantId, List observationId, Instant from, Instant to) {
try (outputStream) {
outputStream.write("[".getBytes(StandardCharsets.UTF_8));
- elasticService.exportData(studyId, outputStream);
+ elasticService.exportData(outputStream, studyId, studyGroupId, participantId, observationId, from, to);
outputStream.write("]".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.error("Cannot export study data for {}", studyId, e);
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java
index 07da6003..d1bdf9a1 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/InterventionService.java
@@ -14,7 +14,6 @@
import io.redlink.more.studymanager.core.factory.TriggerFactory;
import io.redlink.more.studymanager.core.properties.ActionProperties;
import io.redlink.more.studymanager.core.properties.TriggerProperties;
-import io.redlink.more.studymanager.core.validation.ConfigurationValidationReport;
import io.redlink.more.studymanager.exception.BadRequestException;
import io.redlink.more.studymanager.exception.NotFoundException;
import io.redlink.more.studymanager.model.Action;
@@ -215,7 +214,7 @@ private Trigger validateTrigger(Trigger trigger) {
try {
CronExpression.validateExpression(trigger.getProperties().get("cronSchedule").toString());
} catch (ParseException e) {
- throw new ConfigurationValidationException(ConfigurationValidationReport.init().error(e.getMessage()));
+ throw ConfigurationValidationException.ofError(e.getMessage());
}
}
} catch (ConfigurationValidationException e) {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/OAuth2AuthenticationService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/OAuth2AuthenticationService.java
index 64a9abc9..291eebbf 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/OAuth2AuthenticationService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/OAuth2AuthenticationService.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.service;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java
index b2f5b62f..9f47854e 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/ObservationService.java
@@ -11,19 +11,22 @@
import io.redlink.more.studymanager.core.component.Component;
import io.redlink.more.studymanager.core.exception.ConfigurationValidationException;
import io.redlink.more.studymanager.core.factory.ObservationFactory;
+import io.redlink.more.studymanager.core.io.TimeRange;
import io.redlink.more.studymanager.core.properties.ObservationProperties;
+import io.redlink.more.studymanager.core.ui.DataView;
+import io.redlink.more.studymanager.core.ui.DataViewInfo;
+import io.redlink.more.studymanager.core.validation.ValidationIssue;
import io.redlink.more.studymanager.exception.BadRequestException;
import io.redlink.more.studymanager.exception.NotFoundException;
import io.redlink.more.studymanager.model.Observation;
import io.redlink.more.studymanager.model.Study;
import io.redlink.more.studymanager.repository.ObservationRepository;
import io.redlink.more.studymanager.sdk.MoreSDK;
-import java.util.EnumSet;
-import org.springframework.stereotype.Service;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.time.OffsetDateTime;
+import java.util.*;
+
+import org.springframework.stereotype.Service;
@Service
public class ObservationService {
@@ -95,14 +98,51 @@ public void alignObservationsWithStudyState(Study study){
private void deactivateObservationsFor(Study study){ listObservationsFor(study).forEach(Component::deactivate); }
+ private void validateProperties(List observations) {
+ for (Observation observation : observations) {
+ try {
+ factory(observation).validate(observation.getProperties());
+ } catch (ConfigurationValidationException e) {
+ for (ValidationIssue issue : e.getReport().getIssues()) {
+ issue.setComponentTitle(observation.getTitle());
+ }
+
+ throw e;
+ }
+ }
+ }
+
public List listObservationsFor(Study study){
- return listObservations(study.getStudyId()).stream()
- .map(observation -> factory(observation)
- .create(
- sdk.scopedObservationSDK(observation.getStudyId(), observation.getStudyGroupId(), observation.getObservationId()),
- observation.getProperties()
- ))
- .toList();
+ List observations = listObservations(study.getStudyId());
+ List result = new ArrayList<>();
+
+ validateProperties(observations);
+
+ for (Observation observation : observations) {
+ result.add(factory(observation)
+ .create(
+ sdk.scopedObservationSDK(observation.getStudyId(), observation.getStudyGroupId(), observation.getObservationId()),
+ observation.getProperties()
+ ));
+ }
+
+ return result;
+ }
+
+ public DataViewInfo[] listDataViews(Long studyId, Integer observationId) {
+ var obs = getObservation(studyId, observationId).orElseThrow();
+
+ return factory(obs).create(
+ sdk.scopedObservationSDK(obs.getStudyId(), obs.getStudyGroupId(), obs.getObservationId()), obs.getProperties()
+ ).listViews();
+ }
+
+ public DataView queryData(Long studyId, Integer observationId, String viewName, Integer studyGroupId, Integer participantId, TimeRange timerange) {
+ var obs = getObservation(studyId, observationId).orElseThrow();
+
+ return factory(obs).create(
+ sdk.scopedObservationSDK(obs.getStudyId(), obs.getStudyGroupId(), obs.getObservationId()), obs.getProperties()
+ ).getView(viewName, studyGroupId, participantId, timerange);
}
private ObservationFactory factory(Observation observation) {
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyPermissionService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyPermissionService.java
index 0285c2a0..6a228458 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyPermissionService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyPermissionService.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.service;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java
index b520dc83..acd3fb2a 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/StudyService.java
@@ -23,12 +23,9 @@
import io.redlink.more.studymanager.repository.StudyGroupRepository;
import io.redlink.more.studymanager.repository.StudyRepository;
import io.redlink.more.studymanager.repository.UserRepository;
-
-import java.time.temporal.ChronoUnit;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
@@ -118,7 +115,7 @@ public void deleteStudy(Long studyId) {
}
@Transactional
- public void setStatus(Long studyId, Study.Status newState, User user) {
+ public Optional setStatus(Long studyId, Study.Status newState, User user) {
final Study study = getStudy(studyId, user)
.orElseThrow(() -> NotFoundException.Study(studyId));
final Study.Status oldState = study.getStudyState();
@@ -128,8 +125,8 @@ public void setStatus(Long studyId, Study.Status newState, User user) {
throw BadRequestException.StateChange(oldState, newState);
}
- studyRepository.setStateById(studyId, newState)
- .ifPresent(s -> {
+ return studyRepository.setStateById(studyId, newState)
+ .map(s -> {
try {
alignWithStudyState(s);
participantService.listParticipants(studyId).forEach(participant ->
@@ -147,6 +144,7 @@ public void setStatus(Long studyId, Study.Status newState, User user) {
studyRepository.getById(studyId).ifPresent(this::alignWithStudyState);
throw new BadRequestException("Study cannot be initialized", e);
}
+ return s;
});
}
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/service/UserService.java b/studymanager/src/main/java/io/redlink/more/studymanager/service/UserService.java
index 78ed8650..4eb65ba3 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/service/UserService.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/service/UserService.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2022 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.service;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/utils/LoggingUtils.java b/studymanager/src/main/java/io/redlink/more/studymanager/utils/LoggingUtils.java
index 690b3a6f..ec16e05b 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/utils/LoggingUtils.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/utils/LoggingUtils.java
@@ -1,5 +1,10 @@
/*
- * Copyright (c) 2023 Redlink GmbH.
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
*/
package io.redlink.more.studymanager.utils;
diff --git a/studymanager/src/main/java/io/redlink/more/studymanager/utils/SchedulerUtils.java b/studymanager/src/main/java/io/redlink/more/studymanager/utils/SchedulerUtils.java
index d772a74b..1314d986 100644
--- a/studymanager/src/main/java/io/redlink/more/studymanager/utils/SchedulerUtils.java
+++ b/studymanager/src/main/java/io/redlink/more/studymanager/utils/SchedulerUtils.java
@@ -1,3 +1,11 @@
+/*
+ * Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
+ * contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
+ * for Digital Health and Prevention -- A research institute of the
+ * Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
+ * Förderung der wissenschaftlichen Forschung).
+ * Licensed under the Elastic License 2.0.
+ */
package io.redlink.more.studymanager.utils;
import biweekly.component.VEvent;
@@ -7,19 +15,29 @@
import biweekly.util.com.google.ical.compat.javautil.DateIterator;
import io.redlink.more.studymanager.model.Observation;
import io.redlink.more.studymanager.model.Trigger;
-import io.redlink.more.studymanager.model.scheduler.*;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.util.stream.Stream;
-import org.apache.commons.lang3.Range;
-import org.quartz.CronExpression;
-
+import io.redlink.more.studymanager.model.scheduler.Duration;
+import io.redlink.more.studymanager.model.scheduler.Event;
+import io.redlink.more.studymanager.model.scheduler.RecurrenceRule;
+import io.redlink.more.studymanager.model.scheduler.RelativeDate;
+import io.redlink.more.studymanager.model.scheduler.RelativeEvent;
+import io.redlink.more.studymanager.model.scheduler.RelativeRecurrenceRule;
+import io.redlink.more.studymanager.model.scheduler.ScheduleEvent;
import java.sql.Date;
import java.text.ParseException;
import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.Range;
+import org.quartz.CronExpression;
public final class SchedulerUtils {
diff --git a/studymanager/src/main/resources/application.yaml b/studymanager/src/main/resources/application.yaml
index c945047c..8a5cd547 100644
--- a/studymanager/src/main/resources/application.yaml
+++ b/studymanager/src/main/resources/application.yaml
@@ -50,6 +50,9 @@ spring:
flyway:
out-of-order: true
+ mvc:
+ async:
+ request-timeout: -1
server:
forward-headers-strategy: framework
error:
diff --git a/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml b/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml
index 3dfd1569..be8085ce 100644
--- a/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml
+++ b/studymanager/src/main/resources/openapi/StudyManagerAPI.yaml
@@ -11,21 +11,13 @@ servers:
description: Local Test Server
paths:
/components/{componentType}:
+ parameters:
+ - $ref: '#/components/parameters/ComponentType'
get:
tags:
- components
operationId: listComponents
description: List component of certain type
- parameters:
- - name: componentType
- in: path
- schema:
- type: string
- enum:
- - action
- - trigger
- - observation
- required: true
responses:
'200':
description: Components successfully returned
@@ -37,26 +29,18 @@ paths:
$ref: '#/components/schemas/ComponentFactory'
/components/{componentType}/{componentId}/validate:
+ parameters:
+ - $ref: '#/components/parameters/ComponentType'
+ - name: componentId
+ in: path
+ schema:
+ type: string
+ required: true
post:
tags:
- components
description: check if properties are valid for component
operationId: validateProperties
- parameters:
- - name: componentType
- in: path
- schema:
- type: string
- enum:
- - action
- - trigger
- - observation
- required: true
- - name: componentId
- in: path
- schema:
- type: string
- required: true
requestBody:
content:
application/json:
@@ -73,31 +57,23 @@ paths:
description: Not found
/components/{componentType}/{componentId}/api/{slug}:
+ parameters:
+ - $ref: '#/components/parameters/ComponentType'
+ - name: componentId
+ in: path
+ schema:
+ type: string
+ required: true
+ - name: slug
+ in: path
+ schema:
+ type: string
+ required: true
post:
tags:
- components
operationId: accessModuleSpecificEndpoint
description: access module specific endpoint
- parameters:
- - name: componentType
- in: path
- schema:
- type: string
- enum:
- - action
- - trigger
- - observation
- required: true
- - name: componentId
- in: path
- schema:
- type: string
- required: true
- - name: slug
- in: path
- schema:
- type: string
- required: true
requestBody:
required: true
content:
@@ -117,26 +93,18 @@ paths:
description: Error
/components/{componentType}/{componentId}/web-component.js:
+ parameters:
+ - $ref: '#/components/parameters/ComponentType'
+ - name: componentId
+ in: path
+ schema:
+ type: string
+ required: true
get:
tags:
- components
operationId: getWebComponentScript
description: Get web component script
- parameters:
- - name: componentType
- in: path
- schema:
- type: string
- enum:
- - action
- - trigger
- - observation
- required: true
- - name: componentId
- in: path
- schema:
- type: string
- required: true
responses:
'200':
description: Returned script successfully
@@ -253,10 +221,20 @@ paths:
schema:
$ref: '#/components/schemas/StatusChange'
responses:
- '202':
- description: Status changed deleted
+ '200':
+ description: Study status changed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Study'
'400':
- description: Bad request
+ description: The requested transition is not allowed
+ '409':
+ description: Could not update status due to configuration error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ValidationReport'
/studies/{studyId}/collaborators:
parameters:
@@ -395,7 +373,7 @@ paths:
schema:
$ref: '#/components/schemas/StudyTimeline'
'404':
- description: Not found
+ description: Not found
/studies/{studyId}/studyGroups:
@@ -1076,14 +1054,14 @@ paths:
format: binary
responses:
- '201':
- description: import was successful
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Study'
- '400':
- description: bad request
+ '201':
+ description: import was successful
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Study'
+ '400':
+ description: bad request
/studies/{studyId}/export/study:
parameters:
@@ -1095,7 +1073,7 @@ paths:
operationId: exportStudy
responses:
'200':
- description: foo
+ description: Operation successful
content:
application/json:
schema:
@@ -1110,10 +1088,31 @@ paths:
tags:
- importExport
operationId: generateDownloadToken
- description: generate a download token that can be used once for download and is valid for 10 minutes
+ description: Generate a download token that can be used once for download and is valid for 10 minutes
+ parameters:
+ - name: studyGroupId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - name: participantId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - name: observationId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - $ref: '#/components/parameters/From'
+ - $ref: '#/components/parameters/To'
responses:
'200':
- description: foo
+ description: Operation successful
content:
application/json:
schema:
@@ -1124,6 +1123,50 @@ paths:
'404':
description: study not found
+ /studies/{studyId}/export/studydata/{token}:
+ parameters:
+ - $ref: '#/components/parameters/StudyId'
+ - name: token
+ in: path
+ required: true
+ schema:
+ type: string
+ get:
+ tags:
+ - importExport
+ description: Download Study data
+ operationId: exportStudyData
+ parameters:
+ - name: studyGroupId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - name: participantId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - name: observationId
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - $ref: '#/components/parameters/From'
+ - $ref: '#/components/parameters/To'
+ responses:
+ '200':
+ description: Operation successful
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DataExport'
+ '404':
+ description: study not found
+
/studies/{studyId}/import/participants:
parameters:
- $ref: '#/components/parameters/StudyId'
@@ -1157,7 +1200,7 @@ paths:
operationId: exportParticipants
responses:
'200':
- description: foo
+ description: Operation successful
content:
text/csv:
schema:
@@ -1167,13 +1210,13 @@ paths:
description: bad request
/studies/{studyId}/data:
+ parameters:
+ - $ref: '#/components/parameters/StudyId'
get:
tags:
- data
description: Get Study Participation Data
operationId: getParticipationData
- parameters:
- - $ref: '#/components/parameters/StudyId'
responses:
'200':
description: Operation successful
@@ -1186,14 +1229,71 @@ paths:
'404':
description: not found
+ /studies/{studyId}/observations/{observationId}/views:
+ parameters:
+ - $ref: '#/components/parameters/StudyId'
+ - $ref: '#/components/parameters/ObservationId'
+ get:
+ operationId: listObservationViews
+ description: "Get a list of available views"
+ tags:
+ - data
+ responses:
+ 200:
+ description: List of available Views
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ObservationDataView'
+
+ /studies/{studyId}/observations/{observationId}/views/{viewName}:
+ parameters:
+ - $ref: '#/components/parameters/StudyId'
+ - $ref: '#/components/parameters/ObservationId'
+ - name: viewName
+ in: path
+ required: true
+ schema:
+ type: string
+ get:
+ tags:
+ - data
+ description: "Get observation view data"
+ operationId: getObservationViewData
+ parameters:
+ - name: studyGroupId
+ in: query
+ schema:
+ type: integer
+ required: false
+ - name: participantId
+ in: query
+ schema:
+ type: integer
+ required: false
+ - $ref: '#/components/parameters/From'
+ - $ref: '#/components/parameters/To'
+ responses:
+ '200':
+ description: Operation successful
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ObservationDataViewData'
+ '404':
+ description: not found
+
/studies/{studyId}/datapoints:
+ parameters:
+ - $ref: '#/components/parameters/StudyId'
get:
tags:
- data
description: Get Data Points
operationId: getDataPoints
parameters:
- - $ref: '#/components/parameters/StudyId'
- name: observationId
in: query
schema:
@@ -1209,7 +1309,6 @@ paths:
schema:
type: string
format: date-time
- readOnly: true
- name: size
in: query
schema:
@@ -1284,6 +1383,19 @@ paths:
schema:
$ref: '#/components/schemas/FrontendConfiguration'
+ /config/buildInfo:
+ get:
+ tags:
+ - configuration
+ description: retrieve the build info
+ operationId: getBuildInfo
+ responses:
+ 200:
+ description: the build-info
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BuildInfo'
components:
schemas:
@@ -1348,6 +1460,8 @@ components:
type: string
propertyId:
type: string
+ componentTitle:
+ type: string
type:
type: string
@@ -1431,7 +1545,6 @@ components:
- active
- paused
- closed
- readOnly: true
default: draft
StudyGroup:
@@ -1657,6 +1770,52 @@ components:
type: string
format: date-time
readOnly: true
+
+ ObservationDataView:
+ type: object
+ properties:
+ name:
+ type: string
+ label:
+ type: string
+ title:
+ type: string
+ description:
+ type: string
+ required:
+ - name
+
+ ObservationDataViewData:
+ type: object
+ properties:
+ view:
+ $ref: '#/components/schemas/ObservationDataView'
+ chartType:
+ type: string
+ enum:
+ - line
+ - bar
+ - pie
+ labels:
+ type: array
+ items:
+ type: string
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/ObservationDataViewDataRow'
+
+ ObservationDataViewDataRow:
+ type: object
+ properties:
+ label:
+ type: string
+ values:
+ type: array
+ items:
+ type: number
+ format: double
+
DataPoint:
type: object
properties:
@@ -2003,7 +2162,59 @@ components:
- auth
readOnly: true
+ BuildInfo:
+ type: object
+ properties:
+ version:
+ type: string
+ default: '0.0.0'
+ date:
+ type: string
+ format: date-time
+ branch:
+ type: string
+ rev:
+ type: string
+ required:
+ - version
+ - date
+ DataExport:
+ type: array
+ items:
+ type: object
+ properties:
+ datapoint_id:
+ type: string
+ participant_id:
+ type: string
+ study_id:
+ type: string
+ study_group_id:
+ type: string
+ nullable: true
+ observation_id:
+ type: string
+ observation_type:
+ type: string
+ data_type:
+ type: string
+ storage_date:
+ type: string
+ effective_time_frame:
+ type: string
+ additionalProperties: true
+
parameters:
+ ComponentType:
+ name: componentType
+ in: path
+ schema:
+ type: string
+ enum:
+ - action
+ - trigger
+ - observation
+ required: true
StudyId:
name: studyId
in: path
@@ -2052,6 +2263,24 @@ components:
type: integer
format: int32
required: true
+ DateTime:
+ name: dateTime
+ in: query
+ schema:
+ type: string
+ format: date-time
+ From:
+ name: from
+ in: query
+ schema:
+ type: string
+ format: date-time
+ To:
+ name: to
+ in: query
+ schema:
+ type: string
+ format: date-time
securitySchemes:
OAuth:
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/CalendarControllerTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/CalendarControllerTest.java
index 0908610c..1945e85d 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/CalendarControllerTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/CalendarControllerTest.java
@@ -10,6 +10,13 @@
import io.redlink.more.studymanager.model.timeline.StudyTimeline;
import io.redlink.more.studymanager.service.CalendarService;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
import org.apache.commons.lang3.Range;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -21,13 +28,6 @@
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -69,11 +69,11 @@ void testGetStudyTimeline() throws Exception {
LocalDate from = LocalDate.of(2024, 2, 1);
LocalDate to = LocalDate.of(2024, 5, 1);
- when(service.getTimeline(any(), any(), any(), any(OffsetDateTime.class), any(LocalDate.class), any(LocalDate.class)))
+ when(service.getTimeline(any(), any(), any(), any(Instant.class), any(LocalDate.class), any(LocalDate.class)))
.thenAnswer(invocationOnMock -> {
return new StudyTimeline(
referenceDate,
- Range.between(from, to, LocalDate::compareTo),
+ Range.of(from, to, LocalDate::compareTo),
List.of(
ObservationTimelineEvent.fromObservation(
new Observation()
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/InterventionControllerTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/InterventionControllerTest.java
index 0be3b95c..13754225 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/InterventionControllerTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/InterventionControllerTest.java
@@ -16,14 +16,17 @@
import io.redlink.more.studymanager.core.properties.TriggerProperties;
import io.redlink.more.studymanager.model.Action;
import io.redlink.more.studymanager.model.AuthenticatedUser;
-import io.redlink.more.studymanager.model.scheduler.Event;
import io.redlink.more.studymanager.model.Intervention;
import io.redlink.more.studymanager.model.PlatformRole;
import io.redlink.more.studymanager.model.Trigger;
+import io.redlink.more.studymanager.model.scheduler.Event;
import io.redlink.more.studymanager.service.InterventionService;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
import io.redlink.more.studymanager.utils.MapperUtils;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.EnumSet;
+import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -36,17 +39,14 @@
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
-import java.util.Map;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest({InterventionsApiV1Controller.class})
@AutoConfigureMockMvc(addFilters = false)
@@ -102,8 +102,8 @@ void testAddIntervention() throws Exception {
.purpose("some purpose")
.studyGroupId(1)
.schedule(new EventDTO()
- .dtstart(dateStart.atOffset(ZoneOffset.UTC))
- .dtend(dateEnd.atOffset(ZoneOffset.UTC)));
+ .dtstart(dateStart)
+ .dtend(dateEnd));
mvc.perform(post("/api/v1/studies/1/interventions")
.content(mapper.writeValueAsString(interventionRequest))
@@ -142,8 +142,8 @@ void testUpdateIntervention() throws Exception {
.purpose("some purpose")
.title("a title")
.schedule(new EventDTO()
- .dtstart(dateStart.atOffset(ZoneOffset.UTC))
- .dtend(dateEnd.atOffset(ZoneOffset.UTC)));
+ .dtstart(dateStart)
+ .dtend(dateEnd));
mvc.perform(put("/api/v1/studies/1/interventions/1")
.content(mapper.writeValueAsString(interventionRequest))
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/ObservationControllerTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/ObservationControllerTest.java
index a1b91d5d..925a6cf6 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/ObservationControllerTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/controller/studymanager/ObservationControllerTest.java
@@ -12,13 +12,21 @@
import io.redlink.more.studymanager.api.v1.model.EndpointTokenDTO;
import io.redlink.more.studymanager.api.v1.model.ObservationDTO;
import io.redlink.more.studymanager.api.v1.model.ObservationScheduleDTO;
-import io.redlink.more.studymanager.model.*;
+import io.redlink.more.studymanager.model.AuthenticatedUser;
+import io.redlink.more.studymanager.model.EndpointToken;
+import io.redlink.more.studymanager.model.Observation;
+import io.redlink.more.studymanager.model.PlatformRole;
import io.redlink.more.studymanager.model.scheduler.Event;
import io.redlink.more.studymanager.service.IntegrationService;
import io.redlink.more.studymanager.service.OAuth2AuthenticationService;
import io.redlink.more.studymanager.service.ObservationService;
import io.redlink.more.studymanager.utils.MapperUtils;
-import java.time.OffsetDateTime;
+import java.time.Instant;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -29,14 +37,14 @@
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
-import java.time.Instant;
-import java.util.*;
-
-import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -168,7 +176,7 @@ void testAddToken() throws Exception{
EndpointTokenDTO token = new EndpointTokenDTO(
1,
"testLabel",
- OffsetDateTime.now(),
+ Instant.now(),
"test");
when(integrationService.addToken(anyLong(), anyInt(), anyString()))
.thenAnswer(invocationOnMock -> Optional.of(new EndpointToken(
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/CalendarServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/CalendarServiceTest.java
index 49119379..4762d820 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/service/CalendarServiceTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/CalendarServiceTest.java
@@ -14,9 +14,10 @@
import io.redlink.more.studymanager.model.scheduler.RelativeEvent;
import io.redlink.more.studymanager.model.scheduler.RelativeRecurrenceRule;
import io.redlink.more.studymanager.model.timeline.StudyTimeline;
+import java.time.Instant;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.LocalTime;
-import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
@@ -52,7 +53,7 @@ class CalendarServiceTest {
@Test
void testStudyNotFound() {
when(studyService.getStudy(any(), any())).thenReturn(Optional.empty());
- assertThrows(NotFoundException.class, () -> calendarService.getTimeline(1L, 1, 1, OffsetDateTime.now(), LocalDate.now(), LocalDate.now()));
+ assertThrows(NotFoundException.class, () -> calendarService.getTimeline(1L, 1, 1, Instant.now(), LocalDate.now(), LocalDate.now()));
}
@Test
@@ -65,7 +66,7 @@ void testParticipantNotFound() {
.setPlannedStartDate(LocalDate.now())
.setPlannedEndDate(LocalDate.now().plusDays(3))
));
- assertThrows(NotFoundException.class, () -> calendarService.getTimeline(1L, 1,1, OffsetDateTime.now(), LocalDate.now(), LocalDate.now()));
+ assertThrows(NotFoundException.class, () -> calendarService.getTimeline(1L, 1,1, Instant.now(), LocalDate.now(), LocalDate.now()));
}
@Test
@@ -176,10 +177,10 @@ void testGetTimeline() {
1L,
1,
2,
- OffsetDateTime.of(
+ LocalDateTime.of(
LocalDate.of(2024, 5, 11),
- LocalTime.of(10,10,10),
- OffsetDateTime.now().getOffset()),
+ LocalTime.of(10,10,10)
+ ).atZone(ZoneId.systemDefault()).toInstant(),
LocalDate.of(2024, 5, 9),
LocalDate.of(2024,5,17)
);
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/DataProcessingServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/DataProcessingServiceTest.java
index 7c85d0f8..fb247f8a 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/service/DataProcessingServiceTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/DataProcessingServiceTest.java
@@ -10,7 +10,7 @@
import io.redlink.more.studymanager.model.Observation;
import io.redlink.more.studymanager.model.Participant;
-import io.redlink.more.studymanager.model.ParticipationData;
+import io.redlink.more.studymanager.model.data.ParticipationData;
import io.redlink.more.studymanager.model.StudyGroup;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
diff --git a/studymanager/src/test/java/io/redlink/more/studymanager/service/ElasticSearchServiceTest.java b/studymanager/src/test/java/io/redlink/more/studymanager/service/ElasticSearchServiceTest.java
index 47efea77..7b119c98 100644
--- a/studymanager/src/test/java/io/redlink/more/studymanager/service/ElasticSearchServiceTest.java
+++ b/studymanager/src/test/java/io/redlink/more/studymanager/service/ElasticSearchServiceTest.java
@@ -14,18 +14,11 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.io.Resources;
import io.redlink.more.studymanager.configuration.ElasticConfiguration;
-import io.redlink.more.studymanager.model.data.ElasticActionDataPoint;
-import io.redlink.more.studymanager.model.Study;
import io.redlink.more.studymanager.core.io.Timeframe;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.UUID;
-
+import io.redlink.more.studymanager.model.Study;
+import io.redlink.more.studymanager.model.data.ElasticActionDataPoint;
import io.redlink.more.studymanager.model.data.ElasticObservationDataPoint;
import io.redlink.more.studymanager.utils.MapperUtils;
-import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -38,12 +31,17 @@
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
+import java.util.UUID;
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@Testcontainers
@@ -109,14 +107,14 @@ void testRecordAction() {
@Test
void testExportData() throws JsonProcessingException, InterruptedException {
for (int i = 0; i < 1200; i++) {
- setDataPoint(1L, 2, i);
+ setDataPoint(1L, "2", "1", "1", i);
}
//wait for auto commit
Thread.sleep(2000);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try(outputStream) {
outputStream.write("[".getBytes(StandardCharsets.UTF_8));
- elasticService.exportData(1L, outputStream);
+ elasticService.exportData(outputStream, 1L, List.of(2), List.of(1), List.of(1), null, null);
outputStream.write("]".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
//do nothing than close
@@ -126,13 +124,13 @@ void testExportData() throws JsonProcessingException, InterruptedException {
assertThat(MapperUtils.MAPPER.readValue(result, List.class)).hasSize(1200);
}
- private void setDataPoint(Long studyId, int participantId, int i) {
+ private void setDataPoint(Long studyId, String studyGroupId, String participantId, String observationId, int i) {
elasticService.setDataPoint(studyId, new ElasticObservationDataPoint(
"DP_" + studyId + "_" + participantId + "_" + i,
"participant_"+ participantId,
"study_" + studyId,
- null,
- "2",
+ "study_group_" + studyGroupId,
+ observationId,
"acc-mobile-observation",
"acc-mobile-observation",
Instant.now(),