diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotification.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotification.java index 58d7115d24a..a3a4c3fc648 100644 --- a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotification.java +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotification.java @@ -2,6 +2,7 @@ import edu.harvard.iq.dataverse.persistence.JpaEntity; import edu.harvard.iq.dataverse.persistence.config.PostgresJsonConverter; +import org.apache.commons.lang3.StringUtils; import javax.persistence.Column; import javax.persistence.Convert; @@ -54,6 +55,8 @@ public class UserNotification implements Serializable, JpaEntity { @Convert(converter = PostgresJsonConverter.class) private String parameters; + private String searchLabel; + // -------------------- GETTERS -------------------- public Long getId() { @@ -104,6 +107,10 @@ public String getParameters() { return parameters; } + public String getSearchLabel() { + return searchLabel; + } + // -------------------- SETTERS -------------------- public void setId(Long id) { @@ -145,4 +152,8 @@ public void setAdditionalMessage(String returnToAuthorReason) { public void setParameters(String parameters) { this.parameters = parameters; } + + public void setSearchLabel(String searchLabel) { + this.searchLabel = StringUtils.lowerCase(searchLabel); + } } diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQuery.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQuery.java new file mode 100644 index 00000000000..46066c159c2 --- /dev/null +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQuery.java @@ -0,0 +1,77 @@ +package edu.harvard.iq.dataverse.persistence.user; + +/** + * Query parameters used for user notifications. + */ +public class UserNotificationQuery { + + private final static int DEFAULT_RESULT_LIMIT = 10; + + private Long userId; + + private String searchLabel; + + private int offset; + + private int resultLimit = DEFAULT_RESULT_LIMIT; + + private boolean ascending; + + // -------------------- CONSTRUCTORS -------------------- + + private UserNotificationQuery() { + } + + // -------------------- GETTERS -------------------- + + public Long getUserId() { + return userId; + } + + public String getSearchLabel() { + return searchLabel; + } + + public int getOffset() { + return offset; + } + + public int getResultLimit() { + return resultLimit; + } + + public boolean isAscending() { + return ascending; + } + + // -------------------- LOGIC -------------------- + + static public UserNotificationQuery newQuery() { + return new UserNotificationQuery(); + } + + public UserNotificationQuery withUserId(Long userId) { + this.userId = userId; + return this; + } + + public UserNotificationQuery withSearchLabel(String searchLabel) { + this.searchLabel = searchLabel; + return this; + } + + public UserNotificationQuery withOffset(int offset) { + this.offset = offset; + return this; + } + + public UserNotificationQuery withResultLimit(int resultLimit) { + this.resultLimit = resultLimit; + return this; + } + + public UserNotificationQuery withAscending(boolean ascending) { + this.ascending = ascending; + return this; + } +} diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQueryResult.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQueryResult.java new file mode 100644 index 00000000000..8ed816be225 --- /dev/null +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationQueryResult.java @@ -0,0 +1,30 @@ +package edu.harvard.iq.dataverse.persistence.user; + +import java.util.List; + +/** + * Query result including the total count of available results. + */ +public class UserNotificationQueryResult { + + private final List result; + + private final Long totalCount; + + // -------------------- CONSTRUCTORS -------------------- + + public UserNotificationQueryResult(List result, Long totalCount) { + this.result = result; + this.totalCount = totalCount; + } + + // -------------------- GETTERS -------------------- + + public List getResult() { + return result; + } + + public Long getTotalCount() { + return totalCount; + } +} diff --git a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepository.java b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepository.java index 1add64f9b14..675294c2c33 100644 --- a/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepository.java +++ b/dataverse-persistence/src/main/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepository.java @@ -1,18 +1,27 @@ package edu.harvard.iq.dataverse.persistence.user; +import com.google.common.collect.Lists; import edu.harvard.iq.dataverse.persistence.JpaRepository; +import org.apache.commons.lang3.StringUtils; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * @author xyang */ @Stateless public class UserNotificationRepository extends JpaRepository { + private final static int DELETE_BATCH_SIZE = 100; @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; @@ -25,6 +34,39 @@ public UserNotificationRepository() { // -------------------- LOGIC -------------------- + public UserNotificationQueryResult query(UserNotificationQuery queryParam) { + + CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserNotification.class); + Root root = query.from(UserNotification.class); + + List predicates = new ArrayList<>(); + if (StringUtils.isNotBlank(queryParam.getSearchLabel())) { + predicates.add(criteriaBuilder.like(root.get("searchLabel"), "%" + queryParam.getSearchLabel().toLowerCase() + "%")); + } + + if (queryParam.getUserId() != null) { + predicates.add(criteriaBuilder.equal(root.get("user").get("id"), queryParam.getUserId())); + } + + query.select(root) + .where(predicates.toArray(new Predicate[]{})) + .orderBy(queryParam.isAscending() ? criteriaBuilder.asc(root.get("sendDate")) : criteriaBuilder.desc(root.get("sendDate"))); + + List resultList = em.createQuery(query) + .setFirstResult(queryParam.getOffset()) + .setMaxResults(queryParam.getResultLimit()) + .getResultList(); + + CriteriaQuery countQuery = criteriaBuilder.createQuery(Long.class); + countQuery.select(criteriaBuilder.count(root)) + .where(predicates.toArray(new Predicate[]{})); + + Long totalCount = em.createQuery(countQuery).getSingleResult(); + + return new UserNotificationQueryResult(resultList, totalCount); + } + public List findByUser(Long userId) { return em.createQuery("select un from UserNotification un where un.user.id =:userId order by un.sendDate desc", UserNotification.class) .setParameter("userId", userId) @@ -41,6 +83,21 @@ public int updateRequestor(Long oldId, Long newId) { .executeUpdate(); } + public int deleteByIds(Set ids) { + return Lists.partition(Lists.newArrayList(ids), DELETE_BATCH_SIZE).stream() + .mapToInt(idBatch -> + em.createQuery("delete from UserNotification where id in :ids", UserNotification.class) + .setParameter("ids", idBatch) + .executeUpdate()) + .sum(); + } + + public int deleteByUser(Long userId) { + return em.createQuery("delete from UserNotification where user.id = :userId", UserNotification.class) + .setParameter("userId", userId) + .executeUpdate(); + } + public Long getUnreadNotificationCountByUser(Long userId) { return em.createQuery("select count(un) from UserNotification as un where un.user.id = :userId and un.readNotification = :readNotification", Long.class) .setParameter("userId", userId) @@ -65,4 +122,5 @@ public UserNotification findLastSubmitNotificationByObjectId(long datasetId) { .getResultList(); return notifications.isEmpty() ? null : notifications.get(0); } + } diff --git a/dataverse-persistence/src/main/resources/Bundle_en.properties b/dataverse-persistence/src/main/resources/Bundle_en.properties index 67b120619bc..a23cf91eb7f 100755 --- a/dataverse-persistence/src/main/resources/Bundle_en.properties +++ b/dataverse-persistence/src/main/resources/Bundle_en.properties @@ -311,7 +311,17 @@ notification.checksumfail=One or more files in your upload failed checksum valid notification.mail.import.filesystem=Dataset {2} ({0}/dataset.xhtml?persistentId={1}) has been successfully uploaded and verified. notification.import.filesystem=Dataset {1} has been successfully uploaded and verified. notification.import.checksum={1}, dataset had file checksums added via a batch job. -removeNotification=Remove Notification +notification.table.column.detail=Notification details +notification.table.column.sendDate=Created +notification.table.recordsPerPage.title=Records per page +notification.table.empty.message=No notifications found +notification.table.searchTerm.watermark=Filter notifications\u2026 +notification.deleteSelected.btn=Delete selected +notification.deleteSelected=Delete notifications +notification.deleteSelected.info=The notification(s) will be deleted after you click on the Delete button. +notification.numSelected={0} {0, choice, 0#notifications are|1#notification is|2#notifications are} currently selected. +notification.selectAll=Select all {0} notifications +notification.clearSelection=Clear selection. groupAndRoles.manageTips=Here is where you can access and manage all the groups you belong to, and the roles you have been assigned. user.message.signup.label=Create Account user.message.signup.tip=Why have a Dataverse account? To create your own dataverse and customize it, add datasets, or request access to restricted files. diff --git a/dataverse-persistence/src/main/resources/Bundle_pl.properties b/dataverse-persistence/src/main/resources/Bundle_pl.properties index e1888895483..8aa488a3456 100644 --- a/dataverse-persistence/src/main/resources/Bundle_pl.properties +++ b/dataverse-persistence/src/main/resources/Bundle_pl.properties @@ -311,7 +311,17 @@ notification.checksumfail=Co najmniej jeden z przes\u0142anych plik\u00F3w nie p notification.mail.import.filesystem=Zbi\u00F3r danych {2} ({0}/dataset.xhtml?persistentId={1}) zosta\u0142 pomy\u015Blnie przes\u0142any i sprawdzony. notification.import.filesystem=Zbi\u00F3r danych {1} zosta\u0142 pomy\u015Blnie przes\u0142any i sprawdzony. notification.import.checksum=Do plik\u00F3w w zbiorze danych {1} dodano sumy kontrolne przez zadanie asynchroniczne. -removeNotification=Usu\u0144 powiadomienie +notification.table.column.detail=Szczeg\u00F3\u0142y powiadomienia +notification.table.column.sendDate=Stworzony +notification.table.recordsPerPage.title=Rekord\u00F3w na stron\u0119 +notification.table.empty.message=Nie znaleziono \u017Cadnych powiadomie\u0144 +notification.table.searchTerm.watermark=Przefiltruj powiadomienia\u2026 +notification.deleteSelected.btn=Usu\u0144 wybrane +notification.deleteSelected=Usu\u0144 powiadomienia +notification.deleteSelected.info=Powiadomienia zostan\u0105 usuni\u0119ty po naci\u015Bni\u0119ciu przycisku "Usu\u0144". +notification.numSelected=Obecnie wybrano {0} {0,choice,0#powiadomie\u0144|1#powiadomienie|2#powiadomienia|3#powiadomienia|4#powiadomienia|5#powiadomie\u0144} +notification.selectAll=Wybierz wszystkie powiadomienia +notification.clearSelection=Wyczy\u015B\u0107 wyb\u00F3r groupAndRoles.manageTips=Tu masz dost\u0119p do wszystkich grup, do kt\u00F3rych nale\u017Cysz i do przydzielonych Ci r\u00F3l, oraz mo\u017Cliwo\u015B\u0107 zarz\u0105dzania nimi. user.message.signup.label=Za\u0142\u00F3\u017C konto user.message.signup.tip=Maj\u0105c konto w repozytorium mo\u017Cesz za\u0142o\u017Cy\u0107 w\u0142asn\u0105 kolekcj\u0119, dodawa\u0107 zbiory danych i prosi\u0107 o dost\u0119p do zastrze\u017Conych plik\u00F3w. diff --git a/dataverse-persistence/src/main/resources/db/migration/V77__notificationSearchLabel.sql b/dataverse-persistence/src/main/resources/db/migration/V77__notificationSearchLabel.sql new file mode 100644 index 00000000000..a54f6c4ec2e --- /dev/null +++ b/dataverse-persistence/src/main/resources/db/migration/V77__notificationSearchLabel.sql @@ -0,0 +1,3 @@ +ALTER TABLE usernotification ADD COLUMN searchLabel TEXT; + +CREATE INDEX index_usernotification_searchLabel on usernotification (searchLabel); diff --git a/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepositoryIT.java b/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepositoryIT.java index 1d41d6590b2..1667997dbe2 100644 --- a/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepositoryIT.java +++ b/dataverse-persistence/src/test/java/edu/harvard/iq/dataverse/persistence/user/UserNotificationRepositoryIT.java @@ -7,7 +7,6 @@ import org.junit.Test; import javax.inject.Inject; - import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -85,4 +84,52 @@ public void findLastSubmitNotificationForDataset() { assertThat(notification).extracting(UserNotification::getId, UserNotification::getType) .containsExactly(3L, NotificationType.SUBMITTEDDS); } + + @Test + public void query() { + // given + UserNotificationQuery query = UserNotificationQuery.newQuery(); + + // when + UserNotificationQueryResult result = userNotificationRepository.query(query); + + // then + assertThat(result.getResult()).hasSize(5); + assertThat(result.getTotalCount()).isEqualTo(5); + } + + @Test + public void query_paging() { + // given & when + UserNotificationQueryResult page1 = userNotificationRepository.query(UserNotificationQuery.newQuery() + .withUserId(2L) + .withResultLimit(2)); + + // then + assertThat(page1.getResult()).extracting(UserNotification::getId).containsExactly(3L, 2L); + assertThat(page1.getTotalCount()).isEqualTo(3); + + // given & when + UserNotificationQueryResult page2 = userNotificationRepository.query(UserNotificationQuery.newQuery() + .withUserId(2L) + .withOffset(2) + .withResultLimit(2)); + + // then + assertThat(page2.getResult()).extracting(UserNotification::getId).containsExactly(1L); + assertThat(page2.getTotalCount()).isEqualTo(3); + } + + @Test + public void query_filtering() { + // given + UserNotificationQuery query = UserNotificationQuery.newQuery().withSearchLabel("Monkeys"); + + // when + UserNotificationQueryResult result = userNotificationRepository.query(query); + + // then + assertThat(result.getResult()).extracting(UserNotification::getId).containsExactly(4L, 3L); + assertThat(result.getTotalCount()).isEqualTo(2); + } } diff --git a/dataverse-persistence/src/test/resources/dbinit.sql b/dataverse-persistence/src/test/resources/dbinit.sql index 8b653056dc4..140f7b7c8e2 100644 --- a/dataverse-persistence/src/test/resources/dbinit.sql +++ b/dataverse-persistence/src/test/resources/dbinit.sql @@ -366,14 +366,14 @@ VALUES ('{"email": "test@gmail.com"}', 'SEND_NEWSLETTER_EMAIL', 1); -------------------- USER NOTIFICATIONS --------------------- -INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate) -VALUES (1, 2, 'CREATEACC', NULL, true, true, '2020-09-24 13:01:00'); -INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate) -VALUES (2, 2, 'CREATEDV', 19, false, true, '2020-09-25 14:00:00'); -INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters) -VALUES (3, 2, 'SUBMITTEDDS', 44, false, false, '2020-09-26 10:00:00', '{"message":"message to curator", "requestorId":"4"}'); -INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters) -VALUES (4, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"2"}'); +INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, searchlabel) +VALUES (1, 2, 'CREATEACC', NULL, true, true, '2020-09-24 13:01:00', 'joe@champion.de'); +INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, searchlabel) +VALUES (2, 2, 'CREATEDV', 19, false, true, '2020-09-25 14:00:00', 'datauniverse'); +INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters, searchlabel) +VALUES (3, 2, 'SUBMITTEDDS', 44, false, false, '2020-09-26 10:00:00', '{"message":"message to curator", "requestorId":"4"}', 'monkeys dataset'); +INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters, searchlabel) +VALUES (4, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"2"}', 'monkeys dataset'); INSERT INTO usernotification(id, user_id, type, objectid, emailed, readnotification, senddate, parameters) VALUES (5, 4, 'RETURNEDDS', 44, false, false, '2020-09-26 10:10:00', '{"message":"send back to author", "requestorId":"3"}'); -------------------- SUGGESTIONS --------------------- diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/api/Index.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/api/Index.java index be761c82255..6ce35ff407c 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/api/Index.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/api/Index.java @@ -10,6 +10,8 @@ import edu.harvard.iq.dataverse.common.NullSafeJsonBuilder; import edu.harvard.iq.dataverse.dataset.datasetversion.DatasetVersionServiceBean; import edu.harvard.iq.dataverse.dataset.datasetversion.DatasetVersionServiceBean.RetrieveDatasetVersionResponse; +import edu.harvard.iq.dataverse.notification.NotificationObjectResolver; +import edu.harvard.iq.dataverse.notification.NotificationObjectSearchLabelVisitor; import edu.harvard.iq.dataverse.persistence.DvObject; import edu.harvard.iq.dataverse.persistence.datafile.DataFile; import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata; @@ -21,6 +23,10 @@ import edu.harvard.iq.dataverse.persistence.user.GuestUser; import edu.harvard.iq.dataverse.persistence.user.RoleAssignment; import edu.harvard.iq.dataverse.persistence.user.User; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationQuery; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationQueryResult; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationRepository; import edu.harvard.iq.dataverse.search.FileView; import edu.harvard.iq.dataverse.search.SearchException; import edu.harvard.iq.dataverse.search.SearchFields; @@ -52,6 +58,7 @@ import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -102,6 +109,10 @@ public class Index extends AbstractApiBean { private PermissionsSolrDocFactory solrDocFactory; @Inject private DataverseRoleServiceBean rolesSvc; + @Inject + private NotificationObjectResolver notificationObjectResolver; + @Inject + private UserNotificationRepository userNotificationRepository; public static String contentChanged = "contentChanged"; public static String contentIndexed = "contentIndexed"; @@ -727,4 +738,28 @@ public Response getFileMetadataByDatasetId( return ok(data); } + @GET + @Path("usernotifications") + public Response indexUserNotifications(@QueryParam("page") @DefaultValue("1") Integer page, + @QueryParam("pageSize") @DefaultValue("100") Integer pageSize) { + try { + int offset = (page - 1) * pageSize; + UserNotificationQueryResult toIndex = userNotificationRepository.query(UserNotificationQuery.newQuery() + .withOffset(offset) + .withResultLimit(pageSize)); + if (toIndex.getResult().isEmpty()) { + return notFound("No notifications to index."); + } + + for (UserNotification notification : toIndex.getResult()) { + notificationObjectResolver.resolve(NotificationObjectSearchLabelVisitor.onNotification(notification)); + userNotificationRepository.saveFlushAndClear(notification); + } + return ok("Processed " + toIndex.getResult().size() + " of " + toIndex.getTotalCount() + + " notification(s). firstId:" + toIndex.getResult().get(0).getId() + + " lastId:" + toIndex.getResult().get(toIndex.getResult().size() - 1).getId()); + } catch (Exception ex) { + return error(Status.BAD_REQUEST, "error: " + ex.getCause().getMessage() + ex); + } + } } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 546081781cf..fc8d8da217e 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -27,10 +27,10 @@ import edu.harvard.iq.dataverse.persistence.user.ConfirmEmailData; import edu.harvard.iq.dataverse.persistence.user.NotificationType; import edu.harvard.iq.dataverse.persistence.user.UserNameValidator; -import edu.harvard.iq.dataverse.persistence.user.UserNotification; import edu.harvard.iq.dataverse.persistence.user.UserNotificationRepository; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.settings.SettingsWrapper; +import edu.harvard.iq.dataverse.users.LazyUserNotificationsDataModel; import edu.harvard.iq.dataverse.util.JsfHelper; import edu.harvard.iq.dataverse.util.SystemConfig; import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean; @@ -38,7 +38,11 @@ import org.apache.commons.lang.StringUtils; import org.hibernate.validator.constraints.NotBlank; import org.omnifaces.cdi.ViewScoped; +import org.primefaces.event.SelectEvent; import org.primefaces.event.TabChangeEvent; +import org.primefaces.event.ToggleSelectEvent; +import org.primefaces.event.UnselectEvent; +import org.primefaces.model.LazyDataModel; import javax.ejb.EJB; import javax.faces.application.FacesMessage; @@ -53,12 +57,16 @@ import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import static java.util.stream.Collectors.toList; + @ViewScoped @Named("DataverseUserPage") public class DataverseUserPage extends BaseUserPage { @@ -113,7 +121,9 @@ public enum EditMode { @NotBlank(message = "{password.current}") private String currentPassword; - private List notificationsList = new ArrayList<>(); + private LazyDataModel notificationsList; + private Set selectedNotificationIds = new HashSet<>(); + private boolean selectedAllNotifications = false; private int activeIndex; private String selectTab = "somedata"; @@ -155,10 +165,20 @@ public String getInputPassword() { return inputPassword; } - public List getNotificationsList() { + public LazyDataModel getNotificationsList() { return notificationsList; } + public List getSelectedNotifications() { + return notificationsList.getWrappedData().stream() + .filter(notification -> selectedAllNotifications || selectedNotificationIds.contains(notification.getId())) + .collect(toList()); + } + + public boolean getSelectedAllNotifications() { + return selectedAllNotifications; + } + public String getRedirectPage() { return redirectPage; } @@ -453,10 +473,60 @@ public boolean canRemoveNotifications() { return !systemConfig.isReadonlyMode(); } - public String remove(Long notificationId) { - userNotificationRepository.deleteById(notificationId); - notificationsList.removeIf(notification -> notification.getId().equals(notificationId)); - return null; + public void deleteSelectedNotifications() { + if (selectedAllNotifications) { + userNotificationRepository.deleteByUser(currentUser.getId()); + } else { + userNotificationRepository.deleteByIds(selectedNotificationIds); + selectedNotificationIds.clear(); + } + } + + public void onNotificationSelect(SelectEvent event) { + UserNotificationDTO selectedNotification = (UserNotificationDTO) event.getObject(); + selectedNotificationIds.add(selectedNotification.getId()); + setSelectedAllNotifications(false); + } + + public void onNotificationUnSelect(UnselectEvent event) { + UserNotificationDTO selectedNotification = (UserNotificationDTO) event.getObject(); + selectedNotificationIds.remove(selectedNotification.getId()); + setSelectedAllNotifications(false); + } + + public void onNotificationToggleSelectPage(ToggleSelectEvent event) { + selectedAllNotifications = false; + if (event.isSelected()) { + notificationsList.getWrappedData().forEach(d -> selectedNotificationIds.add(d.getId())); + } else { + notificationsList.getWrappedData().forEach(d -> selectedNotificationIds.remove(d.getId())); + } + } + + public void selectAllNotifications() { + selectedAllNotifications = true; + selectedNotificationIds.clear(); + } + + public void clearSelection() { + selectedAllNotifications = false; + selectedNotificationIds.clear(); + } + + public int getNumberOfSelectedNotifications() { + if (selectedAllNotifications) { + return notificationsList.getRowCount(); + } + + return selectedNotificationIds.size(); + } + + public int getPageCount() { + return (notificationsList.getRowCount() / notificationsList.getPageSize()) + 1; + } + + public int getNotificationCount() { + return notificationsList.getRowCount(); } public void onTabChange(TabChangeEvent event) { @@ -604,18 +674,8 @@ private String truncateToFullWord(String input) { } private void displayNotification() { - - notificationsList = new ArrayList<>(); - - for (UserNotification userNotification : userNotificationRepository.findByUser(currentUser.getId())) { - - notificationsList.add(userNotificationMapper.toDTO(userNotification)); - - if (!userNotification.isReadNotification() && !systemConfig.isReadonlyMode()) { - userNotification.setReadNotification(true); - userNotificationRepository.save(userNotification); - } - } + notificationsList = new LazyUserNotificationsDataModel(getCurrentUser(), + userNotificationRepository, userNotificationMapper, !systemConfig.isReadonlyMode()); } // -------------------- SETTERS -------------------- @@ -636,10 +696,18 @@ public void setInputPassword(String inputPassword) { this.inputPassword = inputPassword; } - public void setNotificationsList(List notificationsList) { + public void setNotificationsList(LazyDataModel notificationsList) { this.notificationsList = notificationsList; } + public void setSelectedNotifications(List selectedNotifications) { + // Not really necessary, but needs to exist for the datatable + } + + public void setSelectedAllNotifications(boolean selectedAllNotifications) { + this.selectedAllNotifications = selectedAllNotifications; + } + public void setRedirectPage(String redirectPage) { this.redirectPage = redirectPage; } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolver.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolver.java new file mode 100644 index 00000000000..83519b4d8d3 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolver.java @@ -0,0 +1,153 @@ +package edu.harvard.iq.dataverse.notification; + +import edu.harvard.iq.dataverse.persistence.datafile.DataFile; +import edu.harvard.iq.dataverse.persistence.datafile.DataFileRepository; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadataRepository; +import edu.harvard.iq.dataverse.persistence.dataset.Dataset; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetRepository; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersionRepository; +import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse; +import edu.harvard.iq.dataverse.persistence.dataverse.DataverseRepository; +import edu.harvard.iq.dataverse.persistence.user.NotificationType; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; + +import javax.ejb.Stateless; +import javax.inject.Inject; +import java.util.Optional; + +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.ASSIGNROLE; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.CHECKSUMFAIL; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.CHECKSUMIMPORT; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.CREATEACC; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.CREATEDS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.CREATEDV; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.FILESYSTEMIMPORT; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.GRANTFILEACCESS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.GRANTFILEACCESSINFO; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.MAPLAYERDELETEFAILED; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.MAPLAYERUPDATED; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.PUBLISHEDDS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.REJECTFILEACCESS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.REJECTFILEACCESSINFO; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.REQUESTFILEACCESS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.RETURNEDDS; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.REVOKEROLE; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.SUBMITTEDDS; + +/** + * Lookup of the notification object based on the type of notification + * and registered object id. + */ +@Stateless +public class NotificationObjectResolver { + + @Inject + private DataverseRepository dataverseRepository; + @Inject + private DatasetRepository datasetRepository; + @Inject + private DataFileRepository dataFileRepository; + @Inject + private DatasetVersionRepository datasetVersionRepository; + @Inject + private FileMetadataRepository fileMetadataRepository; + + // -------------------- CONSTRUCTORS -------------------- + + @Deprecated /* JEE requirement*/ + public NotificationObjectResolver() { + } + + public NotificationObjectResolver(DataverseRepository dataverseRepository, DatasetRepository datasetRepository, DataFileRepository dataFileRepository, DatasetVersionRepository datasetVersionRepository, FileMetadataRepository fileMetadataRepository) { + this.dataverseRepository = dataverseRepository; + this.datasetRepository = datasetRepository; + this.dataFileRepository = dataFileRepository; + this.datasetVersionRepository = datasetVersionRepository; + this.fileMetadataRepository = fileMetadataRepository; + } + + // -------------------- LOGIC -------------------- + + public void resolve(NotificationObjectVisitor visitor) { + UserNotification userNotification = visitor.getNotification(); + + Long objectId = userNotification.getObjectId(); + if (objectId == null) { + if (CREATEACC.equals(userNotification.getType())) { + visitor.handleUser(userNotification.getUser()); + } + return; + } + + switch (userNotification.getType()) { + case ASSIGNROLE: + case REVOKEROLE: + // Can either be a dataverse, dataset or datafile, so search all + Optional dataverse = dataverseRepository.findById(objectId); + + if (dataverse.isPresent()) { + visitor.handleDataverse(dataverse.get()); + } else { + Optional dataset = datasetRepository.findById(objectId); + if (dataset.isPresent()) { + visitor.handleDataset(dataset.get()); + } else { + dataFileRepository.findById(objectId).ifPresent(visitor::handleDataFile); + } + } + + break; + case CREATEDV: + dataverseRepository.findById(objectId).ifPresent(visitor::handleDataverse); + break; + + case REQUESTFILEACCESS: + dataFileRepository.findById(objectId) + .map(DataFile::getOwner) + .ifPresent(visitor::handleDataset); + break; + case GRANTFILEACCESS: + case REJECTFILEACCESS: + case CHECKSUMFAIL: + case GRANTFILEACCESSINFO: + case REJECTFILEACCESSINFO: + datasetRepository.findById(objectId).ifPresent(visitor::handleDataset); + break; + + case MAPLAYERUPDATED: + case CREATEDS: + case SUBMITTEDDS: + case PUBLISHEDDS: + case RETURNEDDS: + case FILESYSTEMIMPORT: + case CHECKSUMIMPORT: + datasetVersionRepository.findById(objectId).ifPresent(visitor::handleDatasetVersion); + break; + + case MAPLAYERDELETEFAILED: + fileMetadataRepository.findById(objectId).ifPresent(visitor::handleFileMetadata); + break; + + default: + if (!isBaseNotification(userNotification)) { + datasetRepository.findById(objectId) + .ifPresent(visitor::handleDataset); + } + break; + } + } + + // -------------------- PRIVATE -------------------- + + /** + Returns true if notification display is handled by main Dataverse repository. +

+ Note that external notifications only works if object associated with notification ({@link #getObjectId()}) + is a {@link Dataset} +

+ Notifications should be redesigned to properly support external notifications. + */ + private boolean isBaseNotification(UserNotification userNotification) { + return NotificationType.getTypes().contains(userNotification.getType()); + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectSearchLabelVisitor.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectSearchLabelVisitor.java new file mode 100644 index 00000000000..713c794b0c9 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectSearchLabelVisitor.java @@ -0,0 +1,85 @@ +package edu.harvard.iq.dataverse.notification; + +import edu.harvard.iq.dataverse.persistence.datafile.DataFile; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata; +import edu.harvard.iq.dataverse.persistence.dataset.Dataset; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion; +import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Handler for notification objects, filling the searchLabel with appropriate + * content. + */ +public class NotificationObjectSearchLabelVisitor implements NotificationObjectVisitor { + + private final UserNotification userNotification; + + // -------------------- CONSTRUCTORS -------------------- + + private NotificationObjectSearchLabelVisitor(UserNotification notification) { + this.userNotification = notification; + } + + // -------------------- GETTERS -------------------- + + @Override + public UserNotification getNotification() { + return userNotification; + } + + // -------------------- LOGIC -------------------- + + static public NotificationObjectSearchLabelVisitor onNotification(UserNotification notification) { + return new NotificationObjectSearchLabelVisitor(notification); + } + + + @Override + public void handleDataFile(DataFile dataFile) { + userNotification.setSearchLabel(dataFile.getDisplayName()); + } + + @Override + public void handleFileMetadata(FileMetadata fileMetadata) { + DatasetVersion datasetVersion = fileMetadata.getDatasetVersion(); + Dataverse dataverse = datasetVersion.getDataset().getOwner(); + userNotification.setSearchLabel(concat( + fileMetadata.getLabel(), + datasetVersion.getParsedTitle(), + dataverse.getDisplayName())); + } + + @Override + public void handleDataverse(Dataverse dataverse) { + userNotification.setSearchLabel(dataverse.getDisplayName()); + } + + @Override + public void handleDataset(Dataset dataset) { + Dataverse dataverse = dataset.getOwner(); + userNotification.setSearchLabel(concat(dataset.getDisplayName(), dataverse.getDisplayName())); + } + + @Override + public void handleDatasetVersion(DatasetVersion version) { + Dataverse dataverse = version.getDataset().getOwner(); + userNotification.setSearchLabel(concat(version.getParsedTitle(), dataverse.getDisplayName())); + } + + @Override + public void handleUser(AuthenticatedUser authenticatedUser) { + userNotification.setSearchLabel(authenticatedUser.getEmail()); + } + + // -------------------- PRIVATE -------------------- + + private String concat(String... labels) { + return Arrays.stream(labels).filter(StringUtils::isNotBlank).collect(Collectors.joining(" ")); + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectVisitor.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectVisitor.java new file mode 100644 index 00000000000..13eb6336557 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/NotificationObjectVisitor.java @@ -0,0 +1,32 @@ +package edu.harvard.iq.dataverse.notification; + +import edu.harvard.iq.dataverse.persistence.datafile.DataFile; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata; +import edu.harvard.iq.dataverse.persistence.dataset.Dataset; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion; +import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; + +/** + * Interface allowing to handle the object which triggered the notification. + */ +public interface NotificationObjectVisitor { + + /** + * The visited notification. + */ + UserNotification getNotification(); + + void handleDataFile(DataFile dataFile); + + void handleFileMetadata(FileMetadata fileMetadata); + + void handleDataverse(Dataverse dataverse); + + void handleDataset(Dataset dataset); + + void handleDatasetVersion(DatasetVersion version); + + void handleUser(AuthenticatedUser authenticatedUser); +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/UserNotificationService.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/UserNotificationService.java index 03aa209e625..5bcf792e4c9 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/UserNotificationService.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/UserNotificationService.java @@ -31,6 +31,7 @@ public class UserNotificationService { private EmailNotificationMapper mailMapper; private ExecutorService executorService; private NotificationParametersUtil notificationParametersUtil; + private NotificationObjectResolver notificationObjectResolver; // -------------------- CONSTRUCTORS -------------------- @@ -40,11 +41,13 @@ public UserNotificationService() { } @Inject - public UserNotificationService(UserNotificationRepository userNotificationRepository, MailService mailService, EmailNotificationMapper mailMapper) { + public UserNotificationService(UserNotificationRepository userNotificationRepository, MailService mailService, EmailNotificationMapper mailMapper, + NotificationObjectResolver notificationObjectResolver) { this(); this.userNotificationRepository = userNotificationRepository; this.mailService = mailService; this.mailMapper = mailMapper; + this.notificationObjectResolver = notificationObjectResolver; executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } @@ -122,6 +125,7 @@ private UserNotification createUserNotification(AuthenticatedUser dataverseUser, if (parameters != null && !parameters.isEmpty()) { notificationParametersUtil.setParameters(userNotification, parameters); } + notificationObjectResolver.resolve(NotificationObjectSearchLabelVisitor.onNotification(userNotification)); return userNotification; } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/NotificationObjectDTOVisitor.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/NotificationObjectDTOVisitor.java new file mode 100644 index 00000000000..558729304d3 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/NotificationObjectDTOVisitor.java @@ -0,0 +1,70 @@ +package edu.harvard.iq.dataverse.notification.dto; + +import edu.harvard.iq.dataverse.notification.NotificationObjectVisitor; +import edu.harvard.iq.dataverse.persistence.datafile.DataFile; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata; +import edu.harvard.iq.dataverse.persistence.dataset.Dataset; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion; +import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; + +/** + * Notification object visitor adding the actual object to the DTO. + */ +class NotificationObjectDTOVisitor implements NotificationObjectVisitor { + + private final UserNotification userNotification; + private final UserNotificationDTO notificationDTO; + + // -------------------- CONSTRUCTORS -------------------- + + private NotificationObjectDTOVisitor(UserNotification userNotification, UserNotificationDTO notificationDTO) { + this.userNotification = userNotification; + this.notificationDTO = notificationDTO; + } + + // -------------------- GETTERS -------------------- + + @Override + public UserNotification getNotification() { + return userNotification; + } + + // -------------------- LOGIC -------------------- + + static public NotificationObjectDTOVisitor toDTO(UserNotification userNotification, UserNotificationDTO notificationDTO) { + return new NotificationObjectDTOVisitor(userNotification, notificationDTO); + } + + + @Override + public void handleDataFile(DataFile dataFile) { + notificationDTO.setTheDataFileObject(dataFile); + } + + @Override + public void handleFileMetadata(FileMetadata fileMetadata) { + notificationDTO.setTheFileMetadataObject(fileMetadata); + } + + @Override + public void handleDataverse(Dataverse dataverse) { + notificationDTO.setTheDataverseObject(dataverse); + } + + @Override + public void handleDataset(Dataset dataset) { + notificationDTO.setTheDatasetObject(dataset); + } + + @Override + public void handleDatasetVersion(DatasetVersion version) { + notificationDTO.setTheDatasetVersionObject(version); + } + + @Override + public void handleUser(AuthenticatedUser authenticatedUser) { + notificationDTO.setTheAuthenticatedUserObject(authenticatedUser); + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapper.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapper.java index 3b33e24fe8c..f78a1c9ae53 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapper.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapper.java @@ -2,17 +2,11 @@ import edu.harvard.iq.dataverse.PermissionServiceBean; import edu.harvard.iq.dataverse.common.BundleUtil; +import edu.harvard.iq.dataverse.notification.NotificationObjectResolver; import edu.harvard.iq.dataverse.notification.NotificationParameter; import edu.harvard.iq.dataverse.notification.NotificationParametersUtil; -import edu.harvard.iq.dataverse.notification.UserNotificationService; import edu.harvard.iq.dataverse.persistence.DvObject; -import edu.harvard.iq.dataverse.persistence.datafile.DataFile; -import edu.harvard.iq.dataverse.persistence.datafile.DataFileRepository; -import edu.harvard.iq.dataverse.persistence.datafile.FileMetadataRepository; import edu.harvard.iq.dataverse.persistence.dataset.Dataset; -import edu.harvard.iq.dataverse.persistence.dataset.DatasetRepository; -import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersionRepository; -import edu.harvard.iq.dataverse.persistence.dataverse.DataverseRepository; import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUserRepository; import edu.harvard.iq.dataverse.persistence.user.NotificationType; @@ -25,19 +19,16 @@ import java.util.Map; import java.util.Set; -import static edu.harvard.iq.dataverse.persistence.user.NotificationType.*; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.ASSIGNROLE; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.GRANTFILEACCESSINFO; +import static edu.harvard.iq.dataverse.persistence.user.NotificationType.REVOKEROLE; import static java.util.stream.Collectors.joining; @Stateless public class UserNotificationMapper { - private DataverseRepository dataverseRepository; - private DatasetRepository datasetRepository; - private DatasetVersionRepository datasetVersionRepository; - private DataFileRepository dataFileRepository; - private FileMetadataRepository fileMetadataRepository; private PermissionServiceBean permissionService; - private UserNotificationService userNotificationService; + private NotificationObjectResolver notificationObjectResolver; private AuthenticatedUserRepository authenticatedUserRepository; private NotificationParametersUtil notificationParametersUtil; @@ -49,18 +40,12 @@ public UserNotificationMapper() { } @Inject - public UserNotificationMapper(DataverseRepository dataverseRepository, DatasetRepository datasetRepository, - DatasetVersionRepository datasetVersionRepository, DataFileRepository dataFileRepository, - FileMetadataRepository fileMetadataRepository, PermissionServiceBean permissionService, - UserNotificationService userNotificationService, AuthenticatedUserRepository authenticatedUserRepository) { + public UserNotificationMapper(PermissionServiceBean permissionService, + NotificationObjectResolver notificationObjectResolver, + AuthenticatedUserRepository authenticatedUserRepository) { this(); - this.dataverseRepository = dataverseRepository; - this.datasetRepository = datasetRepository; - this.datasetVersionRepository = datasetVersionRepository; - this.dataFileRepository = dataFileRepository; - this.fileMetadataRepository = fileMetadataRepository; this.permissionService = permissionService; - this.userNotificationService = userNotificationService; + this.notificationObjectResolver = notificationObjectResolver; this.authenticatedUserRepository = authenticatedUserRepository; } @@ -83,7 +68,7 @@ public UserNotificationDTO toDTO(UserNotification userNotification) { ? parameters.get(NotificationParameter.GRANTED_BY.key()) : parameters.get(NotificationParameter.REJECTED_BY.key())); - assignNotificationObject(notificationDTO, userNotification); + notificationObjectResolver.resolve(NotificationObjectDTOVisitor.toDTO(userNotification, notificationDTO)); assignRoles(notificationDTO, userNotification); notificationDTO.setReplyTo(parameters.get(NotificationParameter.REPLY_TO.key())); @@ -101,66 +86,6 @@ private AuthenticatedUser findRequestor(Map parameters) { return authenticatedUserRepository.findById(id).orElse(null); } - private void assignNotificationObject(UserNotificationDTO notificationDTO, UserNotification userNotification) { - Long objectId = userNotification.getObjectId(); - - switch (userNotification.getType()) { - case ASSIGNROLE: - case REVOKEROLE: - // Can either be a dataverse, dataset or datafile, so search all - dataverseRepository.findById(objectId).ifPresent(notificationDTO::setTheDataverseObject); - - if (notificationDTO.getTheObject() == null) { - datasetRepository.findById(objectId).ifPresent(notificationDTO::setTheDatasetObject); - } - if (notificationDTO.getTheObject() == null) { - dataFileRepository.findById(objectId).ifPresent(notificationDTO::setTheDataFileObject); - } - - break; - case CREATEDV: - dataverseRepository.findById(objectId).ifPresent(notificationDTO::setTheDataverseObject); - break; - - case REQUESTFILEACCESS: - dataFileRepository.findById(objectId) - .map(DataFile::getOwner) - .ifPresent(notificationDTO::setTheDatasetObject); - break; - case GRANTFILEACCESS: - case REJECTFILEACCESS: - case CHECKSUMFAIL: - case GRANTFILEACCESSINFO: - case REJECTFILEACCESSINFO: - datasetRepository.findById(objectId).ifPresent(notificationDTO::setTheDatasetObject); - break; - - case MAPLAYERUPDATED: - case CREATEDS: - case SUBMITTEDDS: - case PUBLISHEDDS: - case RETURNEDDS: - case FILESYSTEMIMPORT: - case CHECKSUMIMPORT: - datasetVersionRepository.findById(objectId).ifPresent(notificationDTO::setTheDatasetVersionObject); - break; - - case MAPLAYERDELETEFAILED: - fileMetadataRepository.findById(objectId).ifPresent(notificationDTO::setTheFileMetadataObject); - break; - - case CREATEACC: - notificationDTO.setTheAuthenticatedUserObject(userNotification.getUser()); - break; - default: - if (!isBaseNotification(userNotification)) { - datasetRepository.findById(objectId) - .ifPresent(notificationDTO::setTheDatasetObject); - } - break; - } - } - private void assignRoles(UserNotificationDTO notificationDTO, UserNotification userNotification) { String notificationType = userNotification.getType(); diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/users/LazyUserNotificationsDataModel.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/users/LazyUserNotificationsDataModel.java new file mode 100644 index 00000000000..a1ed276f71b --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/users/LazyUserNotificationsDataModel.java @@ -0,0 +1,87 @@ +package edu.harvard.iq.dataverse.users; + +import edu.harvard.iq.dataverse.notification.dto.UserNotificationDTO; +import edu.harvard.iq.dataverse.notification.dto.UserNotificationMapper; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationQuery; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationQueryResult; +import edu.harvard.iq.dataverse.persistence.user.UserNotificationRepository; +import edu.harvard.iq.dataverse.util.PrimefacesUtil; +import org.apache.commons.lang.StringUtils; +import org.primefaces.model.FilterMeta; +import org.primefaces.model.LazyDataModel; +import org.primefaces.model.SortOrder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class LazyUserNotificationsDataModel extends LazyDataModel { + + private final AuthenticatedUser authenticatedUser; + private final UserNotificationRepository userNotificationRepository; + private final UserNotificationMapper userNotificationMapper; + private final boolean markViewedAsRead; + private Map notifications; + + // -------------------- CONSTRUCTORS -------------------- + + public LazyUserNotificationsDataModel(AuthenticatedUser authenticatedUser, UserNotificationRepository userNotificationRepository, UserNotificationMapper userNotificationMapper, boolean markViewedAsRead) { + this.userNotificationRepository = userNotificationRepository; + this.authenticatedUser = authenticatedUser; + this.userNotificationMapper = userNotificationMapper; + this.markViewedAsRead = markViewedAsRead; + } + + // -------------------- LOGIC -------------------- + + @Override + public UserNotificationDTO getRowData(String rowId) { + return notifications.get(rowId); + } + + @Override + public Object getRowKey(UserNotificationDTO notification) { + return notification.getId().toString(); + } + + @Override + public List load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filterMeta) { + String filterValue = getGlobalFilterValue(filterMeta); + + notifications = new HashMap<>(); + List notificationDTOList = new ArrayList<>(); + + UserNotificationQueryResult result = userNotificationRepository.query(UserNotificationQuery.newQuery() + .withUserId(authenticatedUser.getId()) + .withSearchLabel(filterValue) + .withOffset(first) + .withResultLimit(pageSize) + .withAscending(sortOrder == SortOrder.ASCENDING)); + + for (UserNotification notification : result.getResult()) { + UserNotificationDTO dto = userNotificationMapper.toDTO(notification); + notifications.put(notification.getId().toString(), dto); + notificationDTOList.add(dto); + + if (!notification.isReadNotification() && markViewedAsRead) { + notification.setReadNotification(true); + userNotificationRepository.save(notification); + } + } + + this.setRowCount(result.getTotalCount().intValue()); + + return notificationDTOList; + } + + // -------------------- PRIVATE -------------------- + + private String getGlobalFilterValue(Map filterMeta) { + FilterMeta globalFilter = filterMeta.getOrDefault(PrimefacesUtil.GLOBAL_FILTER_KEY, null); + return Objects.toString(globalFilter != null && globalFilter.getFilterValue() != null ? globalFilter.getFilterValue() : StringUtils.EMPTY); + } +} diff --git a/dataverse-webapp/src/main/webapp/dataverseuser.xhtml b/dataverse-webapp/src/main/webapp/dataverseuser.xhtml index 4ca58f9debc..dde1b92964d 100644 --- a/dataverse-webapp/src/main/webapp/dataverseuser.xhtml +++ b/dataverse-webapp/src/main/webapp/dataverseuser.xhtml @@ -113,263 +113,351 @@ -

- -
-
- - - - - - - - - - - - - - - - - #{bundle['header.guides.user']} - - - - - - - #{item.theObject.getDisplayName()} - - - #{item.theObject.getOwner().getDisplayName()} - - - #{bundle['header.guides.user']} - - - - - - - #{item.theObject.getDataset().getDisplayName()} - - - #{item.theObject.getDataset().getOwner().getDisplayName()} - - - #{bundle['header.guides.user']} - - - - - - - #{item.theObject.getDataset().getDisplayName()} - - - #{item.theObject.getDataset().getOwner().getDisplayName()} - - - #{item.requestorName} - - - #{item.requestorEmail} - - - #{bundle['notification.wasSubmittedForReview.contributorMessage.prefix'] - .concat(" ") - .concat(DataverseUserPage.getLimitedAdditionalMessage(item.additionalMessage))} - - - - - - - #{item.theObject.getDataset().getDisplayName()} - - - #{item.theObject.getDataset().getOwner().getDisplayName()} - - - #{DataverseUserPage.getLimitedAdditionalMessage(item.additionalMessage)} - - - - - #{item.replyTo} - - - - - - - #{item.theObject.getDataset().getDisplayName()} - - - #{item.theObject.getDataset().getOwner().getDisplayName()} - - - - - - - #{item.theObject.displayName} - - - #{item.requestorName} - - - #{item.requestorEmail} - - - - - - - #{item.theObject.displayName} - - - - - - - #{item.theObject.displayName} - - - - - - - #{item.theObject.getDataset().getDisplayName()} - - - - - - - - #{item.theObject.getDatasetVersion().getDataset().getDisplayName()} - - - - - - - - - #{item.theObject.getDisplayName()} - - - - - - - - - - - - - #{item.theObject.getDisplayName()} - - - - - - - - - - - - - - #{item.theObject.getOwner().getDisplayName()} - - - + - - - - - - #{item.theObject.getDisplayName()} - - - - - - - #{item.theObject.getDisplayName()} - - - - - - - #{item.theObject.getOwner().getDisplayName()} - + + + + + +
+ +
+ +
+ + + + + + + +
+
+
+ +
+ + + +
+
+
+
+
+ +
+
+ +
+
+ +

+ + + + +   + + + - - - - - - - - - - - - - - - - - - - + - - - - - #{item.theObject.getDisplayName()} - - - #{systemConfig.dataverseSiteUrl}/permissions-manage-files.xhtml?id=#{item.theObject.getId()} - +   + +

+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + #{bundle['header.guides.user']} + + + + + + + #{item.theObject.getDisplayName()} + + + #{item.theObject.getOwner().getDisplayName()} + + + #{bundle['header.guides.user']} + + + + + + + #{item.theObject.getDataset().getDisplayName()} + + + #{item.theObject.getDataset().getOwner().getDisplayName()} + + + #{bundle['header.guides.user']} + + + + + + + #{item.theObject.getDataset().getDisplayName()} + + + #{item.theObject.getDataset().getOwner().getDisplayName()} + + + #{item.requestorName} + + + #{item.requestorEmail} + + + #{bundle['notification.wasSubmittedForReview.contributorMessage.prefix'] + .concat(" ") + .concat(DataverseUserPage.getLimitedAdditionalMessage(item.additionalMessage))} + + + + + + + #{item.theObject.getDataset().getDisplayName()} + + + #{item.theObject.getDataset().getOwner().getDisplayName()} + + + #{DataverseUserPage.getLimitedAdditionalMessage(item.additionalMessage)} + + + + + #{item.replyTo} + + + + + + + #{item.theObject.getDataset().getDisplayName()} + + + #{item.theObject.getDataset().getOwner().getDisplayName()} + + + + + + + #{item.theObject.displayName} + + + #{item.requestorName} + + + #{item.requestorEmail} + + + + + + + #{item.theObject.displayName} + + + + + + + #{item.theObject.displayName} + + + + + + + #{item.theObject.getDataset().getDisplayName()} + + + + + + + + #{item.theObject.getDatasetVersion().getDataset().getDisplayName()} + + + + + + + + + #{item.theObject.getDisplayName()} + + + + + - - - - - #{item.theObject.getDisplayName()} - - - #{systemConfig.dataverseSiteUrl}/permissions-manage-files.xhtml?id=#{item.theObject.getId()} - + + + + + + #{item.theObject.getDisplayName()} + + + + + + + + + + + #{item.theObject.getOwner().getDisplayName()} + + + - -
-
- - - -
-
-
-
+ + + + + + #{item.theObject.getDisplayName()} + + + + + + + #{item.theObject.getDisplayName()} + + + + + + + #{item.theObject.getOwner().getDisplayName()} + + + + + + + + + + + + + + + + + + + + + + + + + + + #{item.theObject.getDisplayName()} + + + #{systemConfig.dataverseSiteUrl}/permissions-manage-files.xhtml?id=#{item.theObject.getId()} + + + + + + + + #{item.theObject.getDisplayName()} + + + #{systemConfig.dataverseSiteUrl}/permissions-manage-files.xhtml?id=#{item.theObject.getId()} + + + + + + + + + +
@@ -834,6 +922,22 @@
+ + +

+ #{bundle['notification.deleteSelected.info']} +

+
+ + +
+
diff --git a/dataverse-webapp/src/main/webapp/resources/vecler/theme-base.scss b/dataverse-webapp/src/main/webapp/resources/vecler/theme-base.scss index d4b47f74ccb..93c82770ded 100644 --- a/dataverse-webapp/src/main/webapp/resources/vecler/theme-base.scss +++ b/dataverse-webapp/src/main/webapp/resources/vecler/theme-base.scss @@ -1557,6 +1557,14 @@ div[id$="notifications"].ui-tabs-panel { } } +.notification-col-select { + width: 20px; +} + +.notification-col-send-date { + width: 210px; +} + /* Tables */ .visuallyhidden { diff --git a/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolverTest.java b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolverTest.java new file mode 100644 index 00000000000..326486c0061 --- /dev/null +++ b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/NotificationObjectResolverTest.java @@ -0,0 +1,295 @@ +package edu.harvard.iq.dataverse.notification; + +import edu.harvard.iq.dataverse.persistence.MocksFactory; +import edu.harvard.iq.dataverse.persistence.datafile.DataFile; +import edu.harvard.iq.dataverse.persistence.datafile.DataFileRepository; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata; +import edu.harvard.iq.dataverse.persistence.datafile.FileMetadataRepository; +import edu.harvard.iq.dataverse.persistence.dataset.Dataset; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetRepository; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion; +import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersionRepository; +import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse; +import edu.harvard.iq.dataverse.persistence.dataverse.DataverseRepository; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser; +import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUserRepository; +import edu.harvard.iq.dataverse.persistence.user.NotificationType; +import edu.harvard.iq.dataverse.persistence.user.UserNotification; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class NotificationObjectResolverTest { + + private final static Long DATAVERSE_ID = 3L; + private final static Long DATASET_ID = 30L; + private final static Long DATASET_VERSION_ID = 31L; + private final static Long DATAFILE_ID = 300L; + private final static Long FILEMETADATA_ID = 301L; + + @Mock + private DataverseRepository dataverseRepository; + @Mock + private DatasetRepository datasetRepository; + @Mock + private DatasetVersionRepository datasetVersionRepository; + @Mock + private DataFileRepository dataFileRepository; + @Mock + private FileMetadataRepository fileMetadataRepository; + @Mock + private AuthenticatedUserRepository authenticatedUserRepository; + @Mock + private NotificationObjectVisitor notificationObjectVisitor; + + private Dataverse dataverse; + private Dataset dataset; + private DatasetVersion datasetVersion; + private DataFile dataFile; + + private AuthenticatedUser user = MocksFactory.makeAuthenticatedUser("John", "Doe"); + + private NotificationObjectResolver notificationObjectResolver; + + @BeforeEach + void beforeEach() { + notificationObjectResolver = new NotificationObjectResolver(dataverseRepository, datasetRepository, dataFileRepository, datasetVersionRepository, fileMetadataRepository); + + dataverse = new Dataverse(); + dataverse.setId(DATAVERSE_ID); + + dataset = new Dataset(); + dataset.setId(DATASET_ID); + dataset.setOwner(dataverse); + + datasetVersion = dataset.getLatestVersion(); + datasetVersion.setId(DATASET_VERSION_ID); + + dataFile = new DataFile(); + dataFile.setId(DATAFILE_ID); + dataFile.setOwner(dataset); + + FileMetadata fileMetadata = new FileMetadata(); + fileMetadata.setId(FILEMETADATA_ID); + fileMetadata.setDataFile(dataFile); + fileMetadata.setDatasetVersion(datasetVersion); + } + + // -------------------- TESTS -------------------- + @Test + void resolve_ASSIGNROLE_dataverse() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.ASSIGNROLE); + notification.setObjectId(DATAVERSE_ID); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(dataverseRepository.findById(DATAVERSE_ID)).thenReturn(Optional.of(dataverse)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataverse(dataverse); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_ASSIGNROLE_dataset() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.ASSIGNROLE); + notification.setObjectId(DATASET_ID); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(datasetRepository.findById(DATASET_ID)).thenReturn(Optional.of(dataset)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataset(dataset); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_ASSIGNROLE_datafile() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.ASSIGNROLE); + notification.setObjectId(DATAFILE_ID); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(dataFileRepository.findById(DATAFILE_ID)).thenReturn(Optional.of(dataFile)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataFile(dataFile); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_REVOKEROLE_dataset() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.REVOKEROLE); + notification.setObjectId(DATASET_ID); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(datasetRepository.findById(DATASET_ID)).thenReturn(Optional.of(dataset)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataset(dataset); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_CREATEDV() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.CREATEDV); + notification.setObjectId(DATAVERSE_ID); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(dataverseRepository.findById(DATAVERSE_ID)).thenReturn(Optional.of(dataverse)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataverse(dataverse); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_CREATEDS() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.CREATEDS); + notification.setObjectId(DATASET_VERSION_ID); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(datasetVersionRepository.findById(DATASET_VERSION_ID)).thenReturn(Optional.of(datasetVersion)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDatasetVersion(datasetVersion); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_CREATEACC() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.CREATEACC); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleUser(user); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_REQUESTFILEACCESS() { + // given + AuthenticatedUser requestor = MocksFactory.makeAuthenticatedUser("Some", "Requestor"); + requestor.setEmail("sr@example.com"); + Map parameters = new HashMap<>(); + parameters.put(NotificationParameter.REQUESTOR_ID.key(), requestor.getId().toString()); + + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.REQUESTFILEACCESS); + notification.setUser(user); + notification.setParameters("{\"requestorId\":\"" + requestor.getId() + "\"}"); + + notification.setObjectId(DATAFILE_ID); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(dataFileRepository.findById(DATAFILE_ID)).thenReturn(Optional.of(dataFile)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataset(dataset); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_object_not_in_database() { + // given + UserNotification notification = new UserNotification(); + notification.setType(NotificationType.CREATEDV); + notification.setObjectId(DATAVERSE_ID); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).getNotification(); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @Test + void resolve_custom_type() { + // given + UserNotification notification = new UserNotification(); + notification.setObjectId(DATASET_ID); + notification.setType("CUSTOM"); + notification.setUser(user); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(datasetRepository.findById(DATASET_ID)).thenReturn(Optional.of(dataset)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataset(dataset); + verifyNoMoreInteractions(notificationObjectVisitor); + } + + @ParameterizedTest + @ValueSource(strings = {"GRANTFILEACCESSINFO", "REJECTFILEACCESSINFO"}) + void resolve_FILEACCESSINFO(String type) { + // given + UserNotification notification = new UserNotification(); + notification.setObjectId(DATASET_ID); + notification.setType(type); + notification.setUser(user); + notification.setParameters("GRANTFILEACCESSINFO".equals(type) + ? "{\"grantedBy\":\"dataverseAdmin\"}" + : "{\"rejectedBy\":\"dataverseAdmin\"}"); + when(notificationObjectVisitor.getNotification()).thenReturn(notification); + when(datasetRepository.findById(DATASET_ID)).thenReturn(Optional.of(dataset)); + + // when + notificationObjectResolver.resolve(notificationObjectVisitor); + + // then + verify(notificationObjectVisitor, times(1)).handleDataset(dataset); + verifyNoMoreInteractions(notificationObjectVisitor); + } +} diff --git a/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapperTest.java b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapperTest.java index 4ec893edfe8..18a9ba4f64e 100644 --- a/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapperTest.java +++ b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/notification/dto/UserNotificationMapperTest.java @@ -2,6 +2,7 @@ import com.google.common.collect.Sets; import edu.harvard.iq.dataverse.PermissionServiceBean; +import edu.harvard.iq.dataverse.notification.NotificationObjectResolver; import edu.harvard.iq.dataverse.notification.NotificationObjectType; import edu.harvard.iq.dataverse.notification.NotificationParameter; import edu.harvard.iq.dataverse.notification.UserNotificationService; @@ -77,8 +78,9 @@ public class UserNotificationMapperTest { @BeforeEach void beforeEach() { - notificationMapper = new UserNotificationMapper(dataverseRepository, datasetRepository, datasetVersionRepository, dataFileRepository, - fileMetadataRepository, permissionService, new UserNotificationService(), authenticatedUserRepository); + notificationMapper = new UserNotificationMapper(permissionService, + new NotificationObjectResolver(dataverseRepository, datasetRepository, dataFileRepository, datasetVersionRepository, fileMetadataRepository), + authenticatedUserRepository); dataverse = new Dataverse(); dataverse.setId(DATAVERSE_ID);