Skip to content

Commit aa85c7e

Browse files
Be able to attach multiple files on Events and Actions (#4616)
* Make the document import feature able to upload several files at a time #4504 * Add the possibility to attach documents on actions #4502 Change-Id: I7f0c34754ff1af9bc7206c000a5298c0164ce7d4
1 parent 58cca06 commit aa85c7e

File tree

13 files changed

+363
-15
lines changed

13 files changed

+363
-15
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/action/ActionDto.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ public void setActionMeasure(ActionMeasure actionMeasure) {
180180
this.actionMeasure = actionMeasure;
181181
}
182182

183+
public ActionReferenceDto toReference() {
184+
return new ActionReferenceDto(getUuid(), getTitle());
185+
}
186+
183187
public ReferenceDto getContextReference() {
184188

185189
switch (actionContext) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*******************************************************************************
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*******************************************************************************/
18+
package de.symeda.sormas.api.action;
19+
20+
import de.symeda.sormas.api.ReferenceDto;
21+
22+
public class ActionReferenceDto extends ReferenceDto {
23+
24+
private static final long serialVersionUID = 2430932452606853497L;
25+
26+
public ActionReferenceDto() {
27+
28+
}
29+
30+
public ActionReferenceDto(String uuid) {
31+
setUuid(uuid);
32+
}
33+
34+
public ActionReferenceDto(String uuid, String caption) {
35+
setUuid(uuid);
36+
setCaption(caption);
37+
}
38+
39+
@Override
40+
public String getCaption() {
41+
return super.getCaption();
42+
}
43+
}

sormas-api/src/main/java/de/symeda/sormas/api/document/DocumentRelatedEntityType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
public enum DocumentRelatedEntityType {
2020

21+
ACTION,
2122
EVENT;
2223

2324
public String toString() {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ DiseaseTransmissionMode.VECTOR_BORNE = Primarily vector-borne
445445
DiseaseTransmissionMode.UNKNOWN = Unknown
446446

447447
# DocumentRelatedEntityType
448+
DocumentRelatedEntityType.ACTION = Action
448449
DocumentRelatedEntityType.EVENT = Event
449450

450451
# DocumentWorkflow

sormas-base/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,12 @@
719719
<version>0.3.0</version>
720720
</dependency>
721721

722+
<dependency>
723+
<groupId>com.wcs.wcslib</groupId>
724+
<artifactId>wcslib-vaadin-widget-multifileupload</artifactId>
725+
<version>4.0</version>
726+
</dependency>
727+
722728
<!-- ** Vaadin END ** -->
723729

724730
<!-- *** Compile dependencies END *** -->

sormas-ui/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>extended-token-field</artifactId>
6868
</dependency>
6969

70+
<dependency>
71+
<groupId>com.wcs.wcslib</groupId>
72+
<artifactId>wcslib-vaadin-widget-multifileupload</artifactId>
73+
</dependency>
74+
7075
<dependency>
7176
<groupId>joda-time</groupId>
7277
<artifactId>joda-time</artifactId>

sormas-ui/src/main/java/de/symeda/sormas/ui/action/ActionListEntry.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,28 @@
2525
import com.google.common.base.Strings;
2626
import com.vaadin.icons.VaadinIcons;
2727
import com.vaadin.shared.ui.ContentMode;
28+
import com.vaadin.shared.ui.MarginInfo;
2829
import com.vaadin.ui.Alignment;
2930
import com.vaadin.ui.Button;
3031
import com.vaadin.ui.HorizontalLayout;
3132
import com.vaadin.ui.Label;
3233
import com.vaadin.ui.VerticalLayout;
3334
import com.vaadin.ui.themes.ValoTheme;
3435

36+
import de.symeda.sormas.api.FacadeProvider;
3537
import de.symeda.sormas.api.action.ActionDto;
3638
import de.symeda.sormas.api.action.ActionMeasure;
3739
import de.symeda.sormas.api.action.ActionPriority;
3840
import de.symeda.sormas.api.action.ActionStatus;
41+
import de.symeda.sormas.api.document.DocumentRelatedEntityType;
3942
import de.symeda.sormas.api.event.EventHelper;
43+
import de.symeda.sormas.api.feature.FeatureType;
4044
import de.symeda.sormas.api.i18n.Captions;
4145
import de.symeda.sormas.api.i18n.I18nProperties;
46+
import de.symeda.sormas.api.user.UserRight;
4247
import de.symeda.sormas.api.utils.DataHelper;
4348
import de.symeda.sormas.api.utils.HtmlHelper;
49+
import de.symeda.sormas.ui.document.DocumentListComponent;
4450
import de.symeda.sormas.ui.utils.ButtonHelper;
4551
import de.symeda.sormas.ui.utils.CssStyles;
4652
import de.symeda.sormas.ui.utils.DateFormatHelper;
@@ -63,9 +69,8 @@ public ActionListEntry(ActionDto action) {
6369
VerticalLayout withContentLayout = new VerticalLayout();
6470
withContentLayout.setMargin(false);
6571
withContentLayout.setSpacing(false);
66-
withContentLayout.setWidth(100, Unit.PERCENTAGE);
6772
addComponent(withContentLayout);
68-
setExpandRatio(withContentLayout, 1);
73+
setExpandRatio(withContentLayout, 3);
6974

7075
Label measureOrTitle = new Label(
7176
MoreObjects
@@ -181,6 +186,20 @@ public ActionListEntry(ActionDto action) {
181186
}
182187
priorityLabel.addStyleName(statusStyle);
183188
}
189+
190+
if (FacadeProvider.getFeatureConfigurationFacade().isFeatureEnabled(FeatureType.DOCUMENTS)) {
191+
VerticalLayout documentLayout = new VerticalLayout();
192+
documentLayout.setMargin(false);
193+
documentLayout.setSpacing(false);
194+
addComponent(documentLayout);
195+
setExpandRatio(documentLayout, 1);
196+
documentLayout.setMargin(new MarginInfo(true, true, false, false));
197+
198+
// TODO: user rights?
199+
DocumentListComponent documentList = new DocumentListComponent(DocumentRelatedEntityType.ACTION, action.toReference(), UserRight.EVENT_EDIT);
200+
documentList.addStyleName(CssStyles.SIDE_COMPONENT);
201+
documentLayout.addComponent(documentList);
202+
}
184203
}
185204

186205
public void addEditListener(int rowIndex, Button.ClickListener editClickListener) {

sormas-ui/src/main/java/de/symeda/sormas/ui/document/DocumentListComponent.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.IOException;
1919
import java.util.List;
2020

21+
import org.vaadin.hene.popupbutton.PopupButton;
22+
2123
import com.vaadin.icons.VaadinIcons;
2224
import com.vaadin.server.FileDownloader;
2325
import com.vaadin.server.Page;
@@ -28,9 +30,10 @@
2830
import com.vaadin.ui.HorizontalLayout;
2931
import com.vaadin.ui.Label;
3032
import com.vaadin.ui.Notification;
31-
import com.vaadin.ui.Upload;
3233
import com.vaadin.ui.VerticalLayout;
3334
import com.vaadin.ui.themes.ValoTheme;
35+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.MultiFileUpload;
36+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadStateWindow;
3437

3538
import de.symeda.sormas.api.FacadeProvider;
3639
import de.symeda.sormas.api.ReferenceDto;
@@ -43,13 +46,13 @@
4346
import de.symeda.sormas.api.user.UserRight;
4447
import de.symeda.sormas.api.utils.DataHelper;
4548
import de.symeda.sormas.ui.UserProvider;
46-
import de.symeda.sormas.ui.importer.DocumentReceiver;
49+
import de.symeda.sormas.ui.importer.DocumentMultiFileUpload;
50+
import de.symeda.sormas.ui.importer.DocumentUploadFinishedHandler;
4751
import de.symeda.sormas.ui.utils.ButtonHelper;
4852
import de.symeda.sormas.ui.utils.CssStyles;
4953
import de.symeda.sormas.ui.utils.VaadinUiUtil;
5054

5155
public class DocumentListComponent extends VerticalLayout {
52-
5356
private final DocumentRelatedEntityType relatedEntityType;
5457
private final ReferenceDto entityRef;
5558
private final UserRight editRight;
@@ -93,17 +96,26 @@ private Button buildUploadButton() {
9396
uploadLayout.setSpacing(true);
9497
uploadLayout.setMargin(true);
9598
uploadLayout.addStyleName(CssStyles.LAYOUT_MINIMAL);
96-
uploadLayout.setWidth(250, Unit.PIXELS);
97-
98-
DocumentReceiver receiver = new DocumentReceiver(relatedEntityType, entityRef.getUuid(), this::reload);
99-
Upload upload = new Upload("", receiver);
100-
receiver.setUpload(upload);
101-
upload.setButtonCaption(I18nProperties.getCaption(Captions.importImportData));
102-
CssStyles.style(upload, CssStyles.VSPACE_2);
103-
104-
uploadLayout.addComponentsAndExpand(upload);
10599

106-
return ButtonHelper.createIconPopupButton(Captions.documentUploadDocument, VaadinIcons.PLUS_CIRCLE, uploadLayout, ValoTheme.BUTTON_PRIMARY);
100+
PopupButton mainButton =
101+
ButtonHelper.createIconPopupButton(Captions.documentUploadDocument, VaadinIcons.PLUS_CIRCLE, uploadLayout, ValoTheme.BUTTON_PRIMARY);
102+
103+
UploadStateWindow uploadStateWindow = new UploadStateWindow();
104+
MultiFileUpload multiFileUpload = new DocumentMultiFileUpload(() -> {
105+
mainButton.setButtonClickTogglesPopupVisibility(false);
106+
mainButton.setClosePopupOnOutsideClick(false);
107+
}, new DocumentUploadFinishedHandler(relatedEntityType, entityRef.getUuid(), this::reload), uploadStateWindow);
108+
multiFileUpload
109+
.setUploadButtonCaptions(I18nProperties.getCaption(Captions.importImportData), I18nProperties.getCaption(Captions.importImportData));
110+
multiFileUpload.setAllUploadFinishedHandler(() -> {
111+
mainButton.setButtonClickTogglesPopupVisibility(true);
112+
mainButton.setClosePopupOnOutsideClick(true);
113+
mainButton.setPopupVisible(false);
114+
});
115+
116+
uploadLayout.addComponentsAndExpand(multiFileUpload);
117+
118+
return mainButton;
107119
}
108120

109121
private void reload() {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2021 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
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.ui.importer;
17+
18+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.MultiFileUpload;
19+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadFinishedHandler;
20+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadStartedHandler;
21+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadStatePanel;
22+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadStateWindow;
23+
24+
import de.symeda.sormas.ui.SormasUI;
25+
26+
public class DocumentMultiFileUpload extends MultiFileUpload {
27+
28+
public DocumentMultiFileUpload(UploadStartedHandler uploadStartedHandler, UploadFinishedHandler uploadFinishedHandler, UploadStateWindow uploadStateWindow) {
29+
super(uploadStartedHandler, uploadFinishedHandler, uploadStateWindow, true);
30+
31+
// Need to enable Polling or nothing will happen after selecting a file with the MultiFileUpload input
32+
this.addAttachListener(e -> SormasUI.get().access(() -> SormasUI.get().setPollInterval(300)));
33+
this.addDetachListener(e -> SormasUI.get().access(() -> SormasUI.get().setPollInterval(-1)));
34+
}
35+
36+
@Override
37+
protected UploadStatePanel createStatePanel(UploadStateWindow uploadStateWindow) {
38+
return new UploadStatePanel(uploadStateWindow, new DocumentUploadReceiver());
39+
}
40+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2021 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
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.ui.importer;
17+
18+
import java.io.InputStream;
19+
20+
import com.google.common.io.ByteStreams;
21+
import com.vaadin.server.Page;
22+
import com.vaadin.ui.Label;
23+
import com.vaadin.ui.Notification;
24+
import com.wcs.wcslib.vaadin.widget.multifileupload.ui.UploadFinishedHandler;
25+
26+
import de.symeda.sormas.api.FacadeProvider;
27+
import de.symeda.sormas.api.document.DocumentDto;
28+
import de.symeda.sormas.api.document.DocumentRelatedEntityType;
29+
import de.symeda.sormas.api.i18n.Captions;
30+
import de.symeda.sormas.api.i18n.I18nProperties;
31+
import de.symeda.sormas.api.i18n.Strings;
32+
import de.symeda.sormas.ui.UserProvider;
33+
import de.symeda.sormas.ui.utils.VaadinUiUtil;
34+
35+
public class DocumentUploadFinishedHandler implements UploadFinishedHandler {
36+
37+
private final DocumentRelatedEntityType relatedEntityType;
38+
private final String relatedEntityUuid;
39+
private final Runnable callback;
40+
41+
public DocumentUploadFinishedHandler(DocumentRelatedEntityType relatedEntityType, String relatedEntityUuid, Runnable callback) {
42+
this.relatedEntityType = relatedEntityType;
43+
this.relatedEntityUuid = relatedEntityUuid;
44+
this.callback = callback;
45+
}
46+
47+
@Override
48+
public void handleFile(InputStream inputStream, String fileName, String mimeType, long length, int filesLeftInQueue) {
49+
try {
50+
byte[] bytes = ByteStreams.toByteArray(inputStream);
51+
52+
String existing = FacadeProvider.getDocumentFacade().isExistingDocument(relatedEntityType, relatedEntityUuid, fileName);
53+
if (existing != null) {
54+
VaadinUiUtil.showConfirmationPopup(
55+
I18nProperties.getString(Strings.headingFileExists),
56+
new Label(String.format(I18nProperties.getString(Strings.infoDocumentAlreadyExists), fileName)),
57+
I18nProperties.getCaption(Captions.actionConfirm),
58+
I18nProperties.getCaption(Captions.actionCancel),
59+
null,
60+
ok -> {
61+
if (ok) {
62+
FacadeProvider.getDocumentFacade().deleteDocument(existing);
63+
try {
64+
saveDocument(fileName, mimeType, length, relatedEntityType, relatedEntityUuid, bytes);
65+
} catch (Exception e) {
66+
new Notification(
67+
I18nProperties.getString(Strings.headingImportError),
68+
I18nProperties.getString(Strings.messageImportError),
69+
Notification.Type.ERROR_MESSAGE,
70+
false).show(Page.getCurrent());
71+
throw new RuntimeException(e);
72+
}
73+
if (filesLeftInQueue == 0) {
74+
Notification.show(I18nProperties.getString(Strings.headingUploadSuccess), Notification.Type.TRAY_NOTIFICATION);
75+
}
76+
}
77+
if (filesLeftInQueue == 0) {
78+
if (callback != null) {
79+
callback.run();
80+
}
81+
}
82+
});
83+
} else {
84+
saveDocument(fileName, mimeType, length, relatedEntityType, relatedEntityUuid, bytes);
85+
86+
if (filesLeftInQueue == 0) {
87+
Notification.show(I18nProperties.getString(Strings.headingUploadSuccess), Notification.Type.TRAY_NOTIFICATION);
88+
if (callback != null) {
89+
callback.run();
90+
}
91+
}
92+
}
93+
} catch (Exception e) {
94+
new Notification(
95+
I18nProperties.getString(Strings.headingImportError),
96+
I18nProperties.getString(Strings.messageImportError),
97+
Notification.Type.ERROR_MESSAGE,
98+
false).show(Page.getCurrent());
99+
throw new RuntimeException(e);
100+
}
101+
}
102+
103+
private void saveDocument(
104+
String fileName,
105+
String mimeType,
106+
Long length,
107+
DocumentRelatedEntityType relatedEntityType,
108+
String relatedEntityUuid,
109+
byte[] bytes)
110+
throws Exception {
111+
DocumentDto document = DocumentDto.build();
112+
document.setUploadingUser(UserProvider.getCurrent().getUserReference());
113+
document.setName(fileName);
114+
document.setMimeType(mimeType);
115+
document.setSize(length);
116+
document.setRelatedEntityType(relatedEntityType);
117+
document.setRelatedEntityUuid(relatedEntityUuid);
118+
119+
FacadeProvider.getDocumentFacade().saveDocument(document, bytes);
120+
}
121+
}

0 commit comments

Comments
 (0)