Skip to content

Commit d918ad3

Browse files
Merge pull request #13577 from SORMAS-Foundation/bugfix-13548-dashboard-samples-error
Bugfix 13548 dashboard samples error
2 parents 738a87d + 2cf766c commit d918ad3

File tree

18 files changed

+573
-54
lines changed

18 files changed

+573
-54
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/dashboard/SampleDashboardCriteria.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515

1616
package de.symeda.sormas.api.dashboard;
1717

18+
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
1819
import de.symeda.sormas.api.sample.SampleDashboardFilterDateType;
1920
import de.symeda.sormas.api.sample.SampleMaterial;
2021

2122
public class SampleDashboardCriteria extends BaseDashboardCriteria<SampleDashboardCriteria> {
2223

2324
private SampleDashboardFilterDateType sampleDateType;
2425
private SampleMaterial sampleMaterial;
26+
private EnvironmentSampleMaterial environmentSampleMaterial;
2527

2628
private Boolean withNoDisease;
2729

@@ -56,6 +58,14 @@ public Boolean getWithNoDisease() {
5658
public SampleDashboardCriteria withNoDisease(Boolean withNoDisease) {
5759
this.withNoDisease = withNoDisease;
5860

61+
return self;
62+
}
63+
public EnvironmentSampleMaterial getEnvironmentSampleMaterial() {
64+
return environmentSampleMaterial;
65+
}
66+
public SampleDashboardCriteria environmentSampleMaterial(EnvironmentSampleMaterial environmentSampleMaterial) {
67+
this.environmentSampleMaterial = environmentSampleMaterial;
68+
5969
return self;
6070
}
6171
}

sormas-api/src/main/java/de/symeda/sormas/api/dashboard/sample/SampleDashboardFacade.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import javax.ejb.Remote;
2323

2424
import de.symeda.sormas.api.dashboard.SampleDashboardCriteria;
25+
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
2526
import de.symeda.sormas.api.sample.PathogenTestResultType;
2627
import de.symeda.sormas.api.sample.SampleAssociationType;
2728
import de.symeda.sormas.api.sample.SamplePurpose;
@@ -47,4 +48,12 @@ public interface SampleDashboardFacade {
4748
List<MapSampleDto> getSamplesForMap(SampleDashboardCriteria criteria, Set<SampleAssociationType> associationTypes);
4849

4950
List<MapSampleDto> getEnvironmentalSamplesForMap(SampleDashboardCriteria criteria);
51+
52+
Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(SampleDashboardCriteria dashboardCriteria);
53+
54+
Map<SampleShipmentStatus, Long> getEnvironmentalSampleCountsByShipmentStatus(SampleDashboardCriteria dashboardCriteria);
55+
56+
Map<PathogenTestResultType, Long> getEnvironmentalTestResultCountsByResultType(SampleDashboardCriteria dashboardCriteria);
57+
58+
Map<EnvironmentSampleMaterial, Long> getEnvironmentalSampleCounts(SampleDashboardCriteria dashboardCriteria);
5059
}

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Descriptions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public interface Descriptions {
141141
String sampleDashboardCountsBySpecimenCondition = "sampleDashboardCountsBySpecimenCondition";
142142
String sampleDashboardDiseaseFilter = "sampleDashboardDiseaseFilter";
143143
String sampleDashboardDistrictFilter = "sampleDashboardDistrictFilter";
144+
String sampleDashboardHumans = "sampleDashboardHumans";
144145
String sampleDashboardRegionFilter = "sampleDashboardRegionFilter";
145146
String SormasToSormasOptions_comment = "SormasToSormasOptions.comment";
146147
String SurveillanceReport_associatedMessage = "SurveillanceReport.associatedMessage";

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Strings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ public interface Strings {
827827
String headingSaveNotification = "headingSaveNotification";
828828
String headingSaveUser = "headingSaveUser";
829829
String headingSaveUserNotPossible = "headingSaveUserNotPossible";
830+
String headingSearchSample = "headingSearchSample";
830831
String headingSecurityAlert = "headingSecurityAlert";
831832
String headingSeeAllPersons = "headingSeeAllPersons";
832833
String headingSelectCampaign = "headingSelectCampaign";
@@ -1557,6 +1558,7 @@ public interface Strings {
15571558
String messageSampleOpened = "messageSampleOpened";
15581559
String messageSampleSaved = "messageSampleSaved";
15591560
String messageSamplesDeleted = "messageSamplesDeleted";
1561+
String messageSampleSearchWithDisease = "messageSampleSearchWithDisease";
15601562
String messageSamplesRestored = "messageSamplesRestored";
15611563
String messageSelectedPeriodTooLong = "messageSelectedPeriodTooLong";
15621564
String messageSelfReportArchived = "messageSelfReportArchived";

sormas-api/src/main/resources/descriptions.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ sampleDashboardDistrictFilter= The district of the associated Case/Contact/Event
234234
sampleDashboardDiseaseFilter= The disease of the associated Case/Contact/Event participant's event
235235
sampleDashboardCountsByShipmentStatus=Only samples with the purpose external lab testing are considered
236236
sampleDashboardCountsBySpecimenCondition=Only samples received and with the purpose external lab testing are considered
237+
sampleDashboardHumans=Only samples that are associated with human cases are taken into account
237238

238239
aefiDashboardRegionFilter= The region of the associated immunization
239240
aefiDashboardDistrictFilter= The district of the associated immunization

sormas-api/src/main/resources/strings.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ headingCreateNewCaseIssue = Case creation issue
516516
headingCreateNewClinicalVisit = Create new clinical assessment
517517
headingCreateNewContact = Create new contact
518518
headingCreateNewContactIssue = Contact creation issue
519+
headingSearchSample = Sample search issue
519520
headingCreateNewTravelEntry=Create new travel entry
520521
headingCreateNewEnvironment = Create new environment
521522
headingCreateNewEvent = Create new event
@@ -1261,6 +1262,7 @@ messageEventParticipantsDeleted = All selected eligible event participants have
12611262
messageEventParticipantResponsibleJurisdictionUpdated = Changing or removing the responsible jurisdiction of this event participant could make you lose access to its personal details and/or disallow you to edit it in the future. Are you sure you want to proceed?
12621263
messageEventParticipantToCaseWithoutEventDisease=It is not possible to create cases from an event participant if the disease of the event has not been set
12631264
messageEventParticipantToContactWithoutEventDisease=It is not possible to create a contact from an event participant if the disease of the event has not been set
1265+
messageSampleSearchWithDisease=It is not possible to search for environment samples by disease.
12641266
messageEventJurisdictionUpdated = Changing or removing the jurisdiction of this event could make you lose access to its personal details and/or disallow you to edit it in the future. Are you sure you want to proceed?
12651267
messageEventSaved = Event data saved
12661268
messageEventGroupSaved = Event group data saved

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardFacadeEjb.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import de.symeda.sormas.api.dashboard.sample.MapSampleDto;
2828
import de.symeda.sormas.api.dashboard.sample.SampleDashboardFacade;
2929
import de.symeda.sormas.api.dashboard.sample.SampleShipmentStatus;
30+
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
3031
import de.symeda.sormas.api.sample.PathogenTestResultType;
3132
import de.symeda.sormas.api.sample.SampleAssociationType;
3233
import de.symeda.sormas.api.sample.SamplePurpose;
@@ -73,7 +74,7 @@ public Long countSamplesForMap(SampleDashboardCriteria criteria, Set<SampleAssoc
7374

7475
@Override
7576
public Long countEnvironmentalSamplesForMap(SampleDashboardCriteria criteria) {
76-
return sampleDashboardService.countEnvironmentSamplesForMap(criteria);
77+
return sampleDashboardService.countEnvironmentalSamplesForMap(criteria);
7778
}
7879

7980
@Override
@@ -86,6 +87,26 @@ public List<MapSampleDto> getEnvironmentalSamplesForMap(SampleDashboardCriteria
8687
return sampleDashboardService.getEnvironmentalSamplesForMap(criteria);
8788
}
8889

90+
@Override
91+
public Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(SampleDashboardCriteria dashboardCriteria) {
92+
return sampleDashboardService.getEnvironmentalSampleCountsBySpecimenCondition(dashboardCriteria);
93+
}
94+
95+
@Override
96+
public Map<SampleShipmentStatus, Long> getEnvironmentalSampleCountsByShipmentStatus(SampleDashboardCriteria dashboardCriteria) {
97+
return sampleDashboardService.getEnvironmentalSampleCountsByShipmentStatus(dashboardCriteria);
98+
}
99+
100+
@Override
101+
public Map<PathogenTestResultType, Long> getEnvironmentalTestResultCountsByResultType(SampleDashboardCriteria dashboardCriteria) {
102+
return sampleDashboardService.getEnvironmentalTestResultCountsByResultType(dashboardCriteria);
103+
}
104+
105+
@Override
106+
public Map<EnvironmentSampleMaterial, Long> getEnvironmentalSampleCounts(SampleDashboardCriteria dashboardCriteria) {
107+
return sampleDashboardService.getEnvironmentalSampleCounts(dashboardCriteria);
108+
}
109+
89110
@LocalBean
90111
@Stateless
91112
public static class SampleDashboardFacadeEjbLocal extends SampleDashboardFacadeEjb {

sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/sample/SampleDashboardService.java

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import javax.persistence.criteria.Selection;
4646
import javax.persistence.criteria.Subquery;
4747

48+
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
4849
import org.apache.commons.lang3.tuple.Pair;
4950
import org.jetbrains.annotations.NotNull;
5051

@@ -147,6 +148,36 @@ public Map<SpecimenCondition, Long> getSampleCountsBySpecimenCondition(SampleDas
147148
(cb, root) -> cb.and(buildExternalSamplePredicate(cb, root), cb.equal(root.get(Sample.RECEIVED), true)));
148149
}
149150

151+
public Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(SampleDashboardCriteria dashboardCriteria) {
152+
return getEnvironmentalSampleCountsBySpecimenCondition(
153+
EnvironmentSample.SPECIMEN_CONDITION,
154+
SpecimenCondition.class,
155+
dashboardCriteria,
156+
null);
157+
}
158+
159+
private Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(String property, Class<SpecimenCondition> propertyType,
160+
SampleDashboardCriteria dashboardCriteria,
161+
BiFunction<CriteriaBuilder, Root<EnvironmentSample>, Predicate> additionalFilters) {
162+
final CriteriaBuilder cb = em.getCriteriaBuilder();
163+
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
164+
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
165+
EnvironmentSampleJoins joins = new EnvironmentSampleJoins(sample);
166+
final Path<Object> groupingProperty = sample.get(property);
167+
168+
cq.multiselect(groupingProperty, cb.count(sample));
169+
170+
final Predicate criteriaFilter = createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, joins), dashboardCriteria);
171+
cq.where(CriteriaBuilderHelper.and(cb, criteriaFilter, additionalFilters != null ? additionalFilters.apply(cb, sample) : null));
172+
173+
cq.groupBy(groupingProperty);
174+
175+
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
176+
.stream()
177+
.filter(t -> t.get(0) != null)
178+
.collect(Collectors.toMap(t -> propertyType.cast(t.get(0)), t -> (Long) t.get(1)));
179+
}
180+
150181
private <T extends Enum<?>> Map<T, Long> getSampleCountsByEnumProperty(
151182
String property,
152183
Class<T> propertyType,
@@ -188,8 +219,27 @@ public Map<SampleShipmentStatus, Long> getSampleCountsByShipmentStatus(SampleDas
188219
cq.groupBy(shipped, received);
189220

190221
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
191-
.stream()
192-
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
222+
.stream()
223+
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
224+
}
225+
226+
public Map<SampleShipmentStatus, Long> getEnvironmentalSampleCountsByShipmentStatus(SampleDashboardCriteria dashboardCriteria) {
227+
final CriteriaBuilder cb = em.getCriteriaBuilder();
228+
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
229+
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
230+
EnvironmentSampleJoins joins = new EnvironmentSampleJoins(sample);
231+
Path<Boolean> shipped = sample.get(EnvironmentSample.DISPATCHED);
232+
Path<Boolean> received = sample.get(EnvironmentSample.RECEIVED);
233+
cq.multiselect(shipped, received, cb.count(sample));
234+
235+
final Predicate criteriaFilter = createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, joins), dashboardCriteria);
236+
cq.where(criteriaFilter);
237+
238+
cq.groupBy(shipped, received);
239+
240+
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
241+
.stream()
242+
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
193243
}
194244

195245
private SampleShipmentStatus getSampleShipmentStatusByFlags(Boolean shipped, Boolean received) {
@@ -215,6 +265,54 @@ public Map<PathogenTestResultType, Long> getTestResultCountsByResultType(SampleD
215265
.collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));
216266
}
217267

268+
/**
269+
* Returns a map of counts of environment samples by result type.
270+
*
271+
* @param dashboardCriteria
272+
* @return
273+
*/
274+
public Map<PathogenTestResultType, Long> getEnvironmentalTestResultCountsByResultType(SampleDashboardCriteria dashboardCriteria) {
275+
final CriteriaBuilder cb = em.getCriteriaBuilder();
276+
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
277+
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
278+
Join<EnvironmentSample, PathogenTest> pathogenTestJoin = sample.join(EnvironmentSample.PATHOGEN_TESTS, JoinType.LEFT);
279+
final Path<Object> pathogenTestResult = pathogenTestJoin.get(PathogenTest.TEST_RESULT);
280+
EnvironmentSampleJoins joins = new EnvironmentSampleJoins(sample);
281+
cq.multiselect(pathogenTestResult, cb.count(pathogenTestJoin));
282+
283+
final Predicate criteriaFilter = createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, joins), dashboardCriteria);
284+
cq.where(criteriaFilter);
285+
286+
cq.groupBy(pathogenTestResult);
287+
288+
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
289+
.stream()
290+
.filter(t -> t.get(0) != null)
291+
.collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));
292+
}
293+
294+
/**
295+
* Returns a map of counts of environment samples by material type.
296+
*
297+
* @param dashboardCriteria
298+
* @return
299+
*/
300+
public Map<EnvironmentSampleMaterial, Long> getEnvironmentalSampleCounts(SampleDashboardCriteria dashboardCriteria) {
301+
// This method is used to get the counts of environment samples by material type
302+
final CriteriaBuilder cb = em.getCriteriaBuilder();
303+
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
304+
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
305+
final Path<Object> groupingProperty = sample.get(EnvironmentSample.SAMPLE_MATERIAL);
306+
cq.multiselect(groupingProperty, cb.count(sample));
307+
final Predicate criteriaFilter = createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, new EnvironmentSampleJoins(sample)), dashboardCriteria);
308+
cq.where(criteriaFilter);
309+
cq.groupBy(groupingProperty);
310+
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
311+
.stream()
312+
.filter(t -> t.get(0) != null)
313+
.collect(Collectors.toMap(t -> (EnvironmentSampleMaterial) t.get(0), t -> (Long) t.get(1)));
314+
}
315+
218316
private static <J extends ISampleJoins> List<Selection<?>> getCoordinatesSelection(
219317
SampleAssociationType associationType,
220318
J joins,
@@ -287,7 +385,7 @@ public Long countSamplesForMap(SampleDashboardCriteria criteria, Set<SampleAssoc
287385
return QueryHelper.getSingleResult(em, cq);
288386
}
289387

290-
public Long countEnvironmentSamplesForMap(SampleDashboardCriteria criteria) {
388+
public Long countEnvironmentalSamplesForMap(SampleDashboardCriteria criteria) {
291389
final CriteriaBuilder cb = em.getCriteriaBuilder();
292390
final CriteriaQuery<Long> cq = cb.createQuery(Long.class);
293391
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
@@ -474,8 +572,8 @@ private Predicate createEnvironmentSampleFilter(EnvironmentSampleQueryContext qu
474572
filter = CriteriaBuilderHelper.and(cb, filter, dateFilter);
475573
}
476574

477-
if (criteria.getSampleMaterial() != null) {
478-
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(sampleRoot.get(EnvironmentSample.SAMPLE_MATERIAL), criteria.getSampleMaterial()));
575+
if (criteria.getEnvironmentSampleMaterial() != null) {
576+
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(sampleRoot.get(EnvironmentSample.SAMPLE_MATERIAL), criteria.getEnvironmentSampleMaterial()));
479577
}
480578

481579
return CriteriaBuilderHelper.and(

sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diagram/AbstractEpiCurveComponent.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.List;
2323
import java.util.function.Consumer;
2424

25+
import com.vaadin.shared.ui.ContentMode;
26+
import com.vaadin.ui.Component;
2527
import org.vaadin.hene.popupbutton.PopupButton;
2628

2729
import com.vaadin.icons.VaadinIcons;
@@ -51,11 +53,14 @@ public abstract class AbstractEpiCurveComponent<P extends AbstractDashboardDataP
5153
protected final P dashboardDataProvider;
5254
protected final HighChart epiCurveChart;
5355
protected Label epiCurveLabel;
56+
protected Label infoIcon;
5457

5558
// Others
5659
protected EpiCurveGrouping epiCurveGrouping;
5760
private boolean showMinimumEntries;
5861
private Consumer<Boolean> externalExpandListener;
62+
private HorizontalLayout epiCurveHeaderLayout;
63+
private Button expandEpiCurveButton;
5964

6065
public AbstractEpiCurveComponent(P dashboardDataProvider) {
6166

@@ -79,13 +84,36 @@ public AbstractEpiCurveComponent(P dashboardDataProvider) {
7984
//clearAndFillEpiCurveChart();
8085
}
8186

87+
public AbstractEpiCurveComponent(P dashboardDataProvider, String description) {
88+
this(dashboardDataProvider);
89+
90+
infoIcon = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML);
91+
CssStyles.style(infoIcon, CssStyles.H3, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE, CssStyles.HSPACE_LEFT_4);
92+
93+
this.infoIcon.setDescription(I18nProperties.getDescription(description));
94+
epiCurveLabel.setSizeUndefined();
95+
CssStyles.style(epiCurveLabel, CssStyles.H2, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE);
96+
HorizontalLayout labelWithInfoLayout = new HorizontalLayout(epiCurveLabel, infoIcon);
97+
labelWithInfoLayout.setMargin(false);
98+
labelWithInfoLayout.setSpacing(false);
99+
// Replace the original epiCurveLabel with the new one that includes the info icon
100+
// in the header layout
101+
epiCurveHeaderLayout.removeComponent(epiCurveLabel);
102+
epiCurveHeaderLayout.removeComponent(expandEpiCurveButton);
103+
epiCurveHeaderLayout.addComponent(labelWithInfoLayout);
104+
epiCurveHeaderLayout.setComponentAlignment(labelWithInfoLayout, Alignment.BOTTOM_LEFT);
105+
epiCurveHeaderLayout.setExpandRatio(labelWithInfoLayout, 1f);
106+
epiCurveHeaderLayout.addComponent(expandEpiCurveButton);
107+
epiCurveHeaderLayout.setComponentAlignment(expandEpiCurveButton, Alignment.TOP_RIGHT);
108+
}
109+
82110
public void setExpandListener(Consumer<Boolean> listener) {
83111
externalExpandListener = listener;
84112
}
85113

86114
private HorizontalLayout createHeader() {
87115

88-
HorizontalLayout epiCurveHeaderLayout = new HorizontalLayout();
116+
epiCurveHeaderLayout = new HorizontalLayout();
89117
epiCurveHeaderLayout.setWidth(100, Unit.PERCENTAGE);
90118
epiCurveHeaderLayout.setSpacing(true);
91119
CssStyles.style(epiCurveHeaderLayout, CssStyles.VSPACE_4);
@@ -99,7 +127,7 @@ private HorizontalLayout createHeader() {
99127
epiCurveHeaderLayout.setExpandRatio(epiCurveLabel, 1);
100128

101129
// "Expand" and "Collapse" buttons
102-
Button expandEpiCurveButton =
130+
expandEpiCurveButton =
103131
ButtonHelper.createIconButtonWithCaption("expandEpiCurve", "", VaadinIcons.EXPAND, null, CssStyles.BUTTON_SUBTLE, CssStyles.VSPACE_NONE);
104132
Button collapseEpiCurveButton = ButtonHelper
105133
.createIconButtonWithCaption("collapseEpiCurve", "", VaadinIcons.COMPRESS, null, CssStyles.BUTTON_SUBTLE, CssStyles.VSPACE_NONE);

0 commit comments

Comments
 (0)