Skip to content

Commit bb49b6a

Browse files
User is able to access Measurement page and download their template of interest (#493)
1.) Introduce MeasurementMain Component to which the user can navigate via the built in experimentnavigationcomponent. The main page is also accessible via the URL: /projects//experiments//measurements 2.) Introduce MeasurementTemplateList Component which provides all measurementTemplates stored in the resource path of the application (Provided by @sven1103 in #484)
1 parent 00fd975 commit bb49b6a

File tree

12 files changed

+471
-3
lines changed

12 files changed

+471
-3
lines changed

user-interface/frontend/themes/datamanager/components/main.css

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414
grid-area: experimentdetails;
1515
}
1616

17+
.main.measurement {
18+
grid-template-columns: minmax(min-content, 70%) minmax(min-content, 30%);
19+
grid-template-rows: minmax(min-content, 20%) minmax(min-content, 75%);
20+
grid-template-areas:
21+
". measurementtemplatelist"
22+
"measurementdetails measurementdetails";
23+
}
24+
25+
.main.measurement .measurement-template-list-component {
26+
grid-area: measurementtemplatelist;
27+
}
28+
1729
.main.project {
1830
grid-template-columns: minmax(min-content, 80%) minmax(min-content, 25%);
1931
grid-template-rows: auto;

user-interface/frontend/themes/datamanager/components/virtuallist.css

+55
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1+
.measurement-template-list-component .measurement-template-list {
2+
height: fit-content;
3+
}
4+
5+
.measurement-template-list-component .header {
6+
display: flex;
7+
justify-content: space-between;
8+
align-items: center;
9+
gap: var(--lumo-space-m);
10+
}
11+
12+
.measurement-template-list-component .measurement-template-list-item {
13+
display: inline-flex;
14+
align-items: flex-start;
15+
justify-content: space-between;
16+
width: 100%;
17+
column-gap: var(--lumo-space-l);
18+
padding-top: var(--lumo-space-s);
19+
padding-bottom: var(--lumo-space-s);
20+
}
21+
22+
.measurement-template-list-component .measurement-template-list-item .controls {
23+
display: inline-flex;
24+
align-items: end;
25+
justify-items: center;
26+
column-gap: var(--lumo-space-s);
27+
padding-inline: var(--lumo-space-m);
28+
}
29+
30+
.measurement-template-list-component .measurement-template-list-item .file-name {
31+
overflow: hidden;
32+
text-overflow: ellipsis;
33+
}
34+
35+
.measurement-template-list-component .measurement-template-list-item .file-icon {
36+
display: inline-flex;
37+
font-size: smaller;
38+
flex-shrink: 0;
39+
color: var(--lumo-tertiary-text-color);
40+
}
41+
42+
.measurement-template-list-component .measurement-template-list-item .file-info {
43+
display: inline-flex;
44+
flex-direction: column;
45+
gap: var(--lumo-space-s);
46+
overflow: hidden;
47+
}
48+
49+
.measurement-template-list-component .measurement-template-list-item .file-info-with-icon {
50+
display: inline-flex;
51+
gap: var(--lumo-space-s);
52+
overflow: hidden;
53+
align-items: center;
54+
}
55+
156
.offer-list-component .header {
257
display: inline-flex;
358
justify-content: space-between;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package life.qbic.datamanager.templates;
2+
3+
import java.io.IOException;
4+
import java.util.Objects;
5+
6+
/**
7+
* <b>MS measurement template</b>
8+
*
9+
* <p>The Excel spreadsheet containing the required information for mass spectrometry measurement
10+
* registration-</p>
11+
*
12+
* @since 1.0.0
13+
*/
14+
public class MSMeasurementTemplate extends Template {
15+
16+
private static final String MS_MEASUREMENT_TEMPLATE_PATH = "templates/ms_measurement_registration_sheet.xlsx";
17+
18+
private static final String MS_MEASUREMENT_TEMPLATE_FILENAME = "ms_measurement_registration_sheet.xlsx";
19+
20+
private static final String MS_MEASUREMENT_TEMPLATE_DOMAIN_NAME = "Proteomics Template";
21+
22+
public MSMeasurementTemplate() {
23+
}
24+
25+
@Override
26+
public byte[] getContent() {
27+
try {
28+
return Objects.requireNonNull(
29+
getClass().getClassLoader().getResourceAsStream(MS_MEASUREMENT_TEMPLATE_PATH))
30+
.readAllBytes();
31+
} catch (IOException e) {
32+
throw new RuntimeException("Cannot get content for template: " + MS_MEASUREMENT_TEMPLATE_PATH,
33+
e);
34+
}
35+
}
36+
37+
@Override
38+
public String getFileName() {
39+
return MS_MEASUREMENT_TEMPLATE_FILENAME;
40+
}
41+
42+
@Override
43+
public String getDomainName() {
44+
return MS_MEASUREMENT_TEMPLATE_DOMAIN_NAME;
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package life.qbic.datamanager.templates;
2+
3+
import java.io.IOException;
4+
import java.util.Objects;
5+
6+
/**
7+
* <b>NGS measurement template</b>
8+
*
9+
* <p>The Excel spreadsheet containing the required information for measurement registration-</p>
10+
*
11+
* @since 1.0.0
12+
*/
13+
public class NGSMeasurementTemplate extends Template {
14+
15+
private static final String NGS_MEASUREMENT_TEMPLATE_PATH = "templates/ngs_measurement_registration_sheet.xlsx";
16+
17+
private static final String NGS_MEASUREMENT_TEMPLATE_FILENAME = "ngs_measurement_registration_sheet.xlsx";
18+
19+
private static final String NGS_MEASUREMENT_TEMPLATE_DOMAIN_NAME = "Genomics Template";
20+
21+
@Override
22+
public byte[] getContent() {
23+
try {
24+
return Objects.requireNonNull(
25+
getClass().getClassLoader().getResourceAsStream(NGS_MEASUREMENT_TEMPLATE_PATH))
26+
.readAllBytes();
27+
} catch (IOException e) {
28+
throw new RuntimeException(
29+
"Cannot get content for template: " + NGS_MEASUREMENT_TEMPLATE_PATH,
30+
e);
31+
}
32+
}
33+
34+
@Override
35+
public String getFileName() {
36+
return NGS_MEASUREMENT_TEMPLATE_FILENAME;
37+
}
38+
39+
@Override
40+
public String getDomainName() {
41+
return NGS_MEASUREMENT_TEMPLATE_DOMAIN_NAME;
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package life.qbic.datamanager.templates;
2+
3+
import life.qbic.datamanager.views.general.download.DownloadContentProvider;
4+
5+
/**
6+
* <class short description - One Line!>
7+
* <p>
8+
* <More detailed description - When to use, what it solves, etc.>
9+
*
10+
* @since <version tag>
11+
*/
12+
public abstract class Template implements DownloadContentProvider {
13+
14+
public String getDomainName() {
15+
return "Unknown Domain";
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package life.qbic.datamanager.templates;
2+
3+
import life.qbic.datamanager.views.general.download.DownloadContentProvider;
4+
5+
/**
6+
* <b>Template Download Factory</b>
7+
*
8+
* <p>Factory that allows for the generation of different {@link DownloadContentProvider}, for
9+
* example for measurement template spreadsheets.</p>
10+
*
11+
* @since 1.0.0
12+
*/
13+
public class TemplateDownloadFactory {
14+
15+
public static Template provider(TemplateType templateType) {
16+
return
17+
switch (templateType) {
18+
case MS_MEASUREMENT -> new MSMeasurementTemplate();
19+
case NGS_MEASUREMENT -> new NGSMeasurementTemplate();
20+
};
21+
}
22+
23+
public enum TemplateType {
24+
NGS_MEASUREMENT, MS_MEASUREMENT
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package life.qbic.datamanager.views.general.download;
2+
3+
import com.vaadin.flow.component.UI;
4+
import com.vaadin.flow.component.html.Anchor;
5+
import com.vaadin.flow.server.StreamResource;
6+
import java.io.ByteArrayInputStream;
7+
import life.qbic.application.commons.ApplicationException;
8+
import life.qbic.datamanager.templates.Template;
9+
10+
/**
11+
* The MeasurementTemplate download class extends the Anchor class and provides functionality for
12+
* triggering the download of an MeasurementTemplate file.
13+
*/
14+
public class MeasurementTemplateDownload extends Anchor {
15+
16+
public MeasurementTemplateDownload() {
17+
super("_blank", "Download");
18+
/*
19+
* Using setVisible(false), vaadin prevents any client side actions.
20+
* This prevents us from using JavaScript to click the link, which is the only option
21+
* for using anchors now.
22+
* Thus, we prevent the display of the link with `display: none`.
23+
* The link is still on the page but invisible.
24+
*/
25+
getStyle().set("display", "none");
26+
setTarget("_blank");
27+
getElement().setAttribute("download", true);
28+
}
29+
30+
public void trigger(Template template) {
31+
UI ui = getUI().orElseThrow(() -> new ApplicationException(
32+
this.getClass().getSimpleName() + "component triggered but not attached to any UI."));
33+
StreamResource resource = new StreamResource(template.getFileName(),
34+
() -> new ByteArrayInputStream(template.getContent()));
35+
this.setHref(resource);
36+
ui.getPage().executeJs("$0.click()", this.getElement());
37+
}
38+
}

user-interface/src/main/java/life/qbic/datamanager/views/projects/project/experiments/ExperimentNavigationComponent.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020
import java.util.Objects;
2121
import life.qbic.datamanager.views.projects.overview.ProjectOverviewMain;
22+
import life.qbic.datamanager.views.projects.project.measurements.MeasurementMain;
2223
import life.qbic.datamanager.views.projects.project.samples.SampleInformationMain;
2324
import org.slf4j.Logger;
2425

@@ -39,8 +40,8 @@ public class ExperimentNavigationComponent extends Div {
3940
ExperimentInformationMain.class);
4041
RoutingTab<SampleInformationMain> registerSampleBatch = new RoutingTab<>(VaadinIcon.USER.create(),
4142
new Span("Register Sample Batch"), SampleInformationMain.class);
42-
RoutingTab<ProjectOverviewMain> viewMeasurements = new RoutingTab<>(VaadinIcon.USER.create(),
43-
new Span("View Measurements"), ProjectOverviewMain.class);
43+
RoutingTab<MeasurementMain> viewMeasurements = new RoutingTab<>(VaadinIcon.USER.create(),
44+
new Span("View Measurements"), MeasurementMain.class);
4445
RoutingTab<ProjectOverviewMain> downloadAnalysis = new RoutingTab<>(VaadinIcon.USER.create(),
4546
new Span("Download Analysis"), ProjectOverviewMain.class);
4647

@@ -66,7 +67,6 @@ private void initializeSteps() {
6667
}
6768

6869
private void disableUnusedSteps() {
69-
viewMeasurements.setEnabled(false);
7070
downloadAnalysis.setEnabled(false);
7171
}
7272

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package life.qbic.datamanager.views.projects.project.measurements;
2+
3+
import com.vaadin.flow.router.BeforeEnterEvent;
4+
import com.vaadin.flow.router.BeforeEnterObserver;
5+
import com.vaadin.flow.router.Route;
6+
import com.vaadin.flow.spring.annotation.SpringComponent;
7+
import com.vaadin.flow.spring.annotation.UIScope;
8+
import jakarta.annotation.security.PermitAll;
9+
import java.io.Serial;
10+
import java.util.Objects;
11+
import life.qbic.application.commons.ApplicationException;
12+
import life.qbic.datamanager.views.Context;
13+
import life.qbic.datamanager.views.general.Main;
14+
import life.qbic.datamanager.views.general.download.MeasurementTemplateDownload;
15+
import life.qbic.datamanager.views.projects.project.experiments.ExperimentMainLayout;
16+
import life.qbic.datamanager.views.projects.project.measurements.MeasurementTemplateListComponent.DownloadMeasurementTemplateEvent;
17+
import life.qbic.datamanager.views.projects.project.samples.SampleInformationMain;
18+
import life.qbic.logging.api.Logger;
19+
import life.qbic.logging.service.LoggerFactory;
20+
import life.qbic.projectmanagement.domain.model.experiment.Experiment;
21+
import life.qbic.projectmanagement.domain.model.experiment.ExperimentId;
22+
import life.qbic.projectmanagement.domain.model.project.Project;
23+
import life.qbic.projectmanagement.domain.model.project.ProjectId;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
26+
/**
27+
* Measurement Main Component
28+
* <p>
29+
* This component hosts the components necessary to show and update the Measurement information
30+
* associated with an {@link Experiment} within a {@link Project} via the provided
31+
* {@link ExperimentId} and {@link ProjectId} in the URL
32+
*/
33+
34+
@SpringComponent
35+
@UIScope
36+
@Route(value = "projects/:projectId?/experiments/:experimentId?/measurements", layout = ExperimentMainLayout.class)
37+
@PermitAll
38+
public class MeasurementMain extends Main implements BeforeEnterObserver {
39+
40+
@Serial
41+
private static final long serialVersionUID = 3778218989387044758L;
42+
private static final Logger log = LoggerFactory.logger(SampleInformationMain.class);
43+
public static final String PROJECT_ID_ROUTE_PARAMETER = "projectId";
44+
public static final String EXPERIMENT_ID_ROUTE_PARAMETER = "experimentId";
45+
private final MeasurementTemplateListComponent measurementTemplateListComponent;
46+
private final MeasurementTemplateDownload measurementTemplateDownload;
47+
private transient Context context;
48+
49+
public MeasurementMain(
50+
@Autowired MeasurementTemplateListComponent measurementTemplateListComponent) {
51+
Objects.requireNonNull(measurementTemplateListComponent);
52+
this.measurementTemplateListComponent = measurementTemplateListComponent;
53+
measurementTemplateDownload = new MeasurementTemplateDownload();
54+
measurementTemplateListComponent.addDownloadMeasurementTemplateClickListener(
55+
this::onDownloadMeasurementTemplateClicked);
56+
add(measurementTemplateListComponent);
57+
add(measurementTemplateDownload);
58+
addClassName("measurement");
59+
log.debug(String.format(
60+
"New instance for %s(#%s) created with %s(#%s)",
61+
getClass().getSimpleName(), System.identityHashCode(this),
62+
measurementTemplateListComponent.getClass().getSimpleName(),
63+
System.identityHashCode(measurementTemplateListComponent)));
64+
}
65+
66+
/**
67+
* Callback executed before navigation to attaching Component chain is made.
68+
*
69+
* @param event before navigation event with event details
70+
*/
71+
@Override
72+
public void beforeEnter(BeforeEnterEvent event) {
73+
String projectID = event.getRouteParameters().get(PROJECT_ID_ROUTE_PARAMETER)
74+
.orElseThrow();
75+
if (!ProjectId.isValid(projectID)) {
76+
throw new ApplicationException("invalid project id " + projectID);
77+
}
78+
ProjectId parsedProjectId = ProjectId.parse(projectID);
79+
context = new Context().with(parsedProjectId);
80+
String experimentId = event.getRouteParameters().get(EXPERIMENT_ID_ROUTE_PARAMETER)
81+
.orElseThrow();
82+
if (!ExperimentId.isValid(experimentId)) {
83+
throw new ApplicationException("invalid experiment id " + experimentId);
84+
}
85+
ExperimentId parsedExperimentId = ExperimentId.parse(experimentId);
86+
this.context = context.with(parsedExperimentId);
87+
}
88+
89+
private void onDownloadMeasurementTemplateClicked(
90+
DownloadMeasurementTemplateEvent downloadMeasurementTemplateEvent) {
91+
measurementTemplateDownload.trigger(downloadMeasurementTemplateEvent.measurementTemplate());
92+
}
93+
}

0 commit comments

Comments
 (0)