Skip to content

Commit 9a178ee

Browse files
committed
Improved UI Group handling and fixed other issues
- UI Group is now an enum instead of a string tied to Context - Added handling for translation of captions and descriptions - Improved rendering of YesNoUnkown component
1 parent da5d38d commit 9a178ee

17 files changed

Lines changed: 566 additions & 69 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2026 SORMAS Foundation gGmbH
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.api.customizablefield;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
/**
22+
* Defines UI groups for customizable fields, scoped to a specific {@link CustomizableFieldContext}.
23+
* <p>
24+
* Each group has a stable string {@link #key} that is used as:
25+
* <ul>
26+
* <li>the value stored in the database;</li>
27+
* <li>the Vaadin layout location ID when embedding a {@code CustomizableFieldsGroup} component.</li>
28+
* </ul>
29+
* <p>
30+
* To add a new group, append an enum value with the owning context and a unique, stable key.
31+
* The key must not be changed after data has been stored against it.
32+
*/
33+
public enum CustomizableFieldGroup {
34+
35+
// ---- CASE groups --------------------------------------------------------
36+
CASE_TEST_GROUP_1(CustomizableFieldContext.CASE, "caseTestGroup1"),
37+
CASE_TEST_GROUP_2(CustomizableFieldContext.CASE, "caseTestGroup2"),
38+
39+
// ---- EPIDATA groups -----------------------------------------------------
40+
EPIDATA_EXPOSURE_INVESTIGATION(CustomizableFieldContext.EPIDATA, "exposureInvestigation"),
41+
EPIDATA_ACTIVITY_AS_CASE(CustomizableFieldContext.EPIDATA, "activityAsCase"),
42+
EPIDATA_CONTACT_WITH_SOURCE_CASE(CustomizableFieldContext.EPIDATA, "contactWithSourceCase");
43+
44+
private final CustomizableFieldContext context;
45+
/**
46+
* Stable key stored in the database and used as the Vaadin layout location ID.
47+
*/
48+
private final String key;
49+
50+
CustomizableFieldGroup(CustomizableFieldContext context, String key) {
51+
this.context = context;
52+
this.key = key;
53+
}
54+
55+
/**
56+
* The {@link CustomizableFieldContext} this group belongs to.
57+
*/
58+
public CustomizableFieldContext getContext() {
59+
return context;
60+
}
61+
62+
/**
63+
* Stable string key used as the database-stored value and as the Vaadin layout location ID.
64+
*/
65+
public String getKey() {
66+
return key;
67+
}
68+
69+
/**
70+
* Returns all groups that belong to the given context.
71+
*/
72+
public static List<CustomizableFieldGroup> getGroupsForContext(CustomizableFieldContext context) {
73+
List<CustomizableFieldGroup> result = new ArrayList<>();
74+
for (CustomizableFieldGroup group : values()) {
75+
if (group.context == context) {
76+
result.add(group);
77+
}
78+
}
79+
return result;
80+
}
81+
82+
/**
83+
* Looks up a group by its stable {@link #key}, returning {@code null} if not found.
84+
*/
85+
public static CustomizableFieldGroup fromKey(String key) {
86+
if (key == null) {
87+
return null;
88+
}
89+
for (CustomizableFieldGroup group : values()) {
90+
if (group.key.equals(key)) {
91+
return group;
92+
}
93+
}
94+
return null;
95+
}
96+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2026 SORMAS Foundation gGmbH
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.api.customizablefield;
17+
18+
import de.symeda.sormas.api.utils.IgnoreForUrl;
19+
import de.symeda.sormas.api.utils.criteria.BaseCriteria;
20+
21+
/**
22+
* Criteria for filtering customizable field metadata in admin views.
23+
*/
24+
public class CustomizableFieldMetadataCriteria extends BaseCriteria {
25+
26+
private static final long serialVersionUID = 1L;
27+
28+
private String freeTextFilter;
29+
private CustomizableFieldContext contextClass;
30+
private CustomizableFieldType fieldType;
31+
private Boolean active;
32+
33+
public String getFreeTextFilter() {
34+
return freeTextFilter;
35+
}
36+
37+
public CustomizableFieldMetadataCriteria freeTextFilter(String freeTextFilter) {
38+
this.freeTextFilter = freeTextFilter;
39+
return this;
40+
}
41+
42+
@IgnoreForUrl
43+
public CustomizableFieldContext getContextClass() {
44+
return contextClass;
45+
}
46+
47+
public void setContextClass(CustomizableFieldContext contextClass) {
48+
this.contextClass = contextClass;
49+
}
50+
51+
@IgnoreForUrl
52+
public CustomizableFieldType getFieldType() {
53+
return fieldType;
54+
}
55+
56+
public void setFieldType(CustomizableFieldType fieldType) {
57+
this.fieldType = fieldType;
58+
}
59+
60+
@IgnoreForUrl
61+
public Boolean getActive() {
62+
return active;
63+
}
64+
65+
public void setActive(Boolean active) {
66+
this.active = active;
67+
}
68+
}

sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataDto.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
* DTO for customizable field metadata.
3030
* Contains configuration for a custom field that can be added to entities.
3131
*/
32+
@SuppressWarnings({
33+
"java:S1845", // suppress sonar field name clash warning
34+
"java:S2160" // suppress missing equals handled in EnityDto
35+
})
3236
public class CustomizableFieldMetadataDto extends EntityDto {
3337

3438
private static final long serialVersionUID = 1L;
@@ -63,8 +67,7 @@ public class CustomizableFieldMetadataDto extends EntityDto {
6367
@NotNull(message = Validations.required)
6468
private CustomizableFieldContext contextClass;
6569

66-
@Size(max = FieldConstraints.CHARACTER_LIMIT_SMALL)
67-
private String uiGroup;
70+
private CustomizableFieldGroup uiGroup;
6871

6972
private Integer uiLinePosition;
7073
private Float uiLineWeight;
@@ -116,11 +119,11 @@ public void setContextClass(CustomizableFieldContext contextClass) {
116119
this.contextClass = contextClass;
117120
}
118121

119-
public String getUiGroup() {
122+
public CustomizableFieldGroup getUiGroup() {
120123
return uiGroup;
121124
}
122125

123-
public void setUiGroup(String uiGroup) {
126+
public void setUiGroup(CustomizableFieldGroup uiGroup) {
124127
this.uiGroup = uiGroup;
125128
}
126129

sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldMetadataFacade.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import javax.validation.constraints.NotNull;
2323

2424
import de.symeda.sormas.api.common.DeletionDetails;
25+
import de.symeda.sormas.api.utils.SortProperty;
2526

2627
/**
2728
* Facade interface for managing customizable field metadata.
@@ -37,12 +38,12 @@ public interface CustomizableFieldMetadataFacade {
3738
/**
3839
* Get custom fields grouped in a specific UI group
3940
*/
40-
List<CustomizableFieldMetadataDto> getFieldsForUIGroup(String uiGroup);
41+
List<CustomizableFieldMetadataDto> getFieldsForUIGroup(CustomizableFieldGroup uiGroup);
4142

4243
/**
4344
* Get custom fields ordered by UI line position
4445
*/
45-
List<CustomizableFieldMetadataDto> getFieldsOrderedByUIPosition(String uiGroup);
46+
List<CustomizableFieldMetadataDto> getFieldsOrderedByUIPosition(CustomizableFieldGroup uiGroup);
4647

4748
/**
4849
* Find field by name within a specific context
@@ -74,6 +75,20 @@ public interface CustomizableFieldMetadataFacade {
7475
*/
7576
List<CustomizableFieldMetadataDto> getAll();
7677

78+
/**
79+
* Get a paged and filtered list of field metadata for admin views.
80+
*/
81+
List<CustomizableFieldMetadataDto> getIndexList(
82+
CustomizableFieldMetadataCriteria criteria,
83+
Integer first,
84+
Integer max,
85+
List<SortProperty> sortProperties);
86+
87+
/**
88+
* Count field metadata matching the given criteria.
89+
*/
90+
long count(CustomizableFieldMetadataCriteria criteria);
91+
7792
/**
7893
* Save a customizable field metadata
7994
*/

sormas-api/src/main/java/de/symeda/sormas/api/customizablefield/CustomizableFieldVisibilityContext.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

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

18+
import java.io.Serializable;
19+
import java.util.Objects;
1820
import java.util.Optional;
1921

2022
import de.symeda.sormas.api.Disease;
@@ -37,24 +39,44 @@
3739
* CustomizableFieldVisibilityContext ctx = new CustomizableFieldVisibilityContext().withDisease(caze.getDisease());
3840
* </pre>
3941
*/
40-
public class CustomizableFieldVisibilityContext {
42+
public class CustomizableFieldVisibilityContext implements Serializable {
4143

42-
private Optional<Disease> disease = Optional.empty();
44+
private static final long serialVersionUID = 1L;
45+
46+
private Disease disease;
4347

4448
public CustomizableFieldVisibilityContext() {
4549
// no-arg constructor required by deserialization frameworks
4650
}
4751

4852
public Optional<Disease> getDisease() {
49-
return disease;
53+
return Optional.ofNullable(disease);
5054
}
5155

52-
public void setDisease(Optional<Disease> disease) {
56+
public void setDisease(Disease disease) {
5357
this.disease = disease;
5458
}
5559

5660
public CustomizableFieldVisibilityContext withDisease(Disease disease) {
57-
this.disease = Optional.ofNullable(disease);
61+
this.disease = disease;
5862
return this;
5963
}
64+
65+
@Override
66+
public boolean equals(Object o) {
67+
if (this == o) {
68+
return true;
69+
}
70+
if (o == null || getClass() != o.getClass()) {
71+
return false;
72+
}
73+
CustomizableFieldVisibilityContext that = (CustomizableFieldVisibilityContext) o;
74+
return Objects.equals(disease, that.disease);
75+
}
76+
77+
@Override
78+
public int hashCode() {
79+
return Objects.hash(disease);
80+
}
81+
6082
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2026 SORMAS Foundation gGmbH
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.backend.customizablefield;
17+
18+
import javax.persistence.AttributeConverter;
19+
import javax.persistence.Converter;
20+
21+
import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup;
22+
23+
@Converter(autoApply = false)
24+
public class CustomizableFieldGroupConverter implements AttributeConverter<CustomizableFieldGroup, String> {
25+
26+
@Override
27+
public String convertToDatabaseColumn(CustomizableFieldGroup attribute) {
28+
return attribute != null ? attribute.getKey() : null;
29+
}
30+
31+
@Override
32+
public CustomizableFieldGroup convertToEntityAttribute(String dbData) {
33+
return dbData != null ? CustomizableFieldGroup.fromKey(dbData) : null;
34+
}
35+
}

sormas-backend/src/main/java/de/symeda/sormas/backend/customizablefield/CustomizableFieldMetadata.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323

2424
import org.hibernate.annotations.Type;
2525
import org.hibernate.annotations.TypeDef;
26-
import org.hibernate.annotations.TypeDefs;
2726

2827
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
2928

3029
import de.symeda.sormas.api.customizablefield.CustomizableFieldContext;
30+
import de.symeda.sormas.api.customizablefield.CustomizableFieldGroup;
3131
import de.symeda.sormas.api.customizablefield.CustomizableFieldType;
3232
import de.symeda.sormas.backend.common.DeletableAdo;
3333

@@ -36,8 +36,11 @@
3636
* Stores the configuration for custom fields that can be added to entities.
3737
*/
3838
@Entity(name = "customizablefieldmetadata")
39-
@TypeDefs({
40-
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) })
39+
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
40+
@SuppressWarnings({
41+
"java:S1845", // suppress sonar field name clash warning
42+
"java:S2160" // suppress missing equals handled in AbstractDomainObject
43+
})
4144
public class CustomizableFieldMetadata extends DeletableAdo {
4245

4346
private static final long serialVersionUID = 1L;
@@ -60,7 +63,7 @@ public class CustomizableFieldMetadata extends DeletableAdo {
6063
private String description;
6164
private CustomizableFieldType fieldType;
6265
private CustomizableFieldContext contextClass;
63-
private String uiGroup;
66+
private CustomizableFieldGroup uiGroup;
6467
private Integer uiLinePosition;
6568
private Float uiLineWeight;
6669
private boolean active = true;
@@ -112,11 +115,12 @@ public void setContextClass(CustomizableFieldContext contextClass) {
112115
}
113116

114117
@Column(length = 256)
115-
public String getUiGroup() {
118+
@Convert(converter = CustomizableFieldGroupConverter.class)
119+
public CustomizableFieldGroup getUiGroup() {
116120
return uiGroup;
117121
}
118122

119-
public void setUiGroup(String uiGroup) {
123+
public void setUiGroup(CustomizableFieldGroup uiGroup) {
120124
this.uiGroup = uiGroup;
121125
}
122126

0 commit comments

Comments
 (0)