From 4878379313b91e3eccd572aaddfc9c55be0d66ae Mon Sep 17 00:00:00 2001 From: ErykKul Date: Thu, 24 Mar 2022 16:09:43 +0100 Subject: [PATCH 01/45] New feature for muting email and/or in-app notifications by users for given types of notifications --- .../iq/dataverse/UserNotification.java | 37 +++++++++++++++++- .../UserNotificationServiceBean.java | 14 ++++++- .../harvard/iq/dataverse/UserServiceBean.java | 6 ++- .../providers/builtin/DataverseUserPage.java | 39 +++++++++++++++++++ .../users/AuthenticatedUser.java | 39 ++++++++++++++++++- .../iq/dataverse/userdata/UserUtil.java | 18 +++++++++ src/main/java/propertyFiles/Bundle.properties | 29 ++++++++++++++ .../V5.10.0.1__7492-muting-notifications.sql | 5 +++ src/main/webapp/dataverseuser.xhtml | 27 +++++++++++++ 9 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/db/migration/V5.10.0.1__7492-muting-notifications.sql diff --git a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java index 58152f6673e..72622b11942 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java @@ -1,11 +1,14 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.DateUtil; import java.io.Serializable; import java.sql.Timestamp; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Enumerated; @@ -30,7 +33,39 @@ public enum Type { ASSIGNROLE, REVOKEROLE, CREATEDV, CREATEDS, CREATEACC, SUBMITTEDDS, RETURNEDDS, PUBLISHEDDS, REQUESTFILEACCESS, GRANTFILEACCESS, REJECTFILEACCESS, FILESYSTEMIMPORT, CHECKSUMIMPORT, CHECKSUMFAIL, CONFIRMEMAIL, APIGENERATED, INGESTCOMPLETED, INGESTCOMPLETEDWITHERRORS, - PUBLISHFAILED_PIDREG, WORKFLOW_SUCCESS, WORKFLOW_FAILURE, STATUSUPDATED, DATASETCREATED + PUBLISHFAILED_PIDREG, WORKFLOW_SUCCESS, WORKFLOW_FAILURE, STATUSUPDATED, DATASETCREATED; + + public String getDescription() { + return BundleUtil.getStringFromBundle("notification.typeDescription." + this.name()); + } + + public long flagValue() { + return 1 << this.ordinal(); + } + + public static List fromFlag(Long flag) { + final List types = new ArrayList(); + if (flag == null) { + return types; + } + for (final Type t : values()) { + if ((flag & t.flagValue()) > 0) { + types.add(t); + } + } + return types; + } + + public static Long toFlag(final List types) { + if (types == null || types.isEmpty()) { + return null; + } + long flag = 0; + for (final Type t : types) { + flag |= t.flagValue(); + } + return flag; + } }; private static final long serialVersionUID = 1L; diff --git a/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java index 071805d3d26..610b2f49e87 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java @@ -110,12 +110,22 @@ public void sendNotification(AuthenticatedUser dataverseUser, Timestamp sendDate userNotification.setObjectId(objectId); userNotification.setRequestor(requestor); - if (mailService.sendNotificationEmail(userNotification, comment, requestor, isHtmlContent)) { + if (!isEmailMuted(userNotification) && mailService.sendNotificationEmail(userNotification, comment, requestor, isHtmlContent)) { logger.fine("email was sent"); userNotification.setEmailed(true); } else { logger.fine("email was not sent"); } - save(userNotification); + if (!isNotificationMuted(userNotification)) { + save(userNotification); + } + } + + public boolean isEmailMuted(UserNotification userNotification) { + return userNotification.getUser().hasEmailMuted(userNotification.getType()); + } + + public boolean isNotificationMuted(UserNotification userNotification) { + return userNotification.getUser().hasNotificationMuted(userNotification.getType()); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/UserServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/UserServiceBean.java index 9ec0527a318..02477c032a1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserServiceBean.java @@ -144,6 +144,9 @@ private AuthenticatedUser createAuthenticatedUserForView (Object[] dbRowValues, user.setDeactivated((Boolean)(dbRowValues[13])); user.setDeactivatedTime(UserUtil.getTimestampOrNull(dbRowValues[14])); + user.setMutedEmails((Long)(dbRowValues[15])); + user.setMutedNotifications((Long)(dbRowValues[16])); + user.setRoles(roles); return user; } @@ -415,7 +418,8 @@ private List getUserListCore(String searchTerm, qstr += " u.position,"; qstr += " u.createdtime, u.lastlogintime, u.lastapiusetime, "; qstr += " prov.id, prov.factoryalias, "; - qstr += " u.deactivated, u.deactivatedtime "; + qstr += " u.deactivated, u.deactivatedtime, "; + qstr += " u.mutedEmails, u.mutedNotifications "; qstr += " FROM authenticateduser u,"; qstr += " authenticateduserlookup prov_lookup,"; qstr += " authenticationproviderrow prov"; diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 177c59c5873..4c5122c2a7d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -48,6 +48,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.ejb.EJB; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; @@ -134,6 +135,11 @@ public enum EditMode { private String username; boolean nonLocalLoginEnabled; private List passwordErrors; + + + private List notificationTypeList; + private List mutedEmailList; + private List mutedNotificationList; public String init() { @@ -161,6 +167,11 @@ public String init() { setCurrentUser((AuthenticatedUser) session.getUser()); userAuthProvider = authenticationService.lookupProvider(currentUser); notificationsList = userNotificationService.findByUser(currentUser.getId()); + notificationTypeList = Arrays.asList(UserNotification.Type.values()).stream() + .filter(x -> x.getDescription() != null && !x.getDescription().isEmpty()) + .collect(Collectors.toList()); + mutedEmailList = UserNotification.Type.fromFlag(currentUser.getMutedEmails()); + mutedNotificationList = UserNotification.Type.fromFlag(currentUser.getMutedNotifications()); switch (selectTab) { case "notifications": @@ -368,6 +379,8 @@ public String save() { logger.info("Redirecting"); return permissionsWrapper.notAuthorized() + "faces-redirect=true"; }else { + currentUser.setMutedEmails(UserNotification.Type.toFlag(mutedEmailList)); + currentUser.setMutedNotifications(UserNotification.Type.toFlag(mutedNotificationList)); String emailBeforeUpdate = currentUser.getEmail(); AuthenticatedUser savedUser = authenticationService.updateAuthenticatedUser(currentUser, userDisplayInfo); String emailAfterUpdate = savedUser.getEmail(); @@ -708,4 +721,30 @@ public String getRequestorEmail(UserNotification notification) { if(notification.getRequestor() == null) return BundleUtil.getStringFromBundle("notification.email.info.unavailable");; return notification.getRequestor().getEmail() != null ? notification.getRequestor().getEmail() : BundleUtil.getStringFromBundle("notification.email.info.unavailable"); } + + public List getNotificationTypeList() { + return notificationTypeList; + } + + public void setNotificationTypeList(List notificationTypeList) { + this.notificationTypeList = notificationTypeList; + } + + public List getMutedEmailList() { + return mutedEmailList; + } + + public void setMutedEmailList(List mutedEmailList) { + this.mutedEmailList = mutedEmailList; + } + + public List getMutedNotificationList() { + return mutedNotificationList; + } + + public void setMutedNotificationList(List mutedNotificationList) { + this.mutedNotificationList = mutedNotificationList; + } + + } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index 9d76ce0e47c..ccad45c87ff 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -4,7 +4,6 @@ import edu.harvard.iq.dataverse.DatasetLock; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.ValidateEmail; -import edu.harvard.iq.dataverse.authorization.AccessRequest; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserLookup; import edu.harvard.iq.dataverse.authorization.providers.oauth2.OAuth2TokenData; @@ -121,6 +120,12 @@ public class AuthenticatedUser implements User, Serializable { @Column(nullable=true) private Timestamp deactivatedTime; + @Column(nullable=true) + private Long mutedEmails; + + @Column(nullable=true) + private Long mutedNotifications; + /** * @todo Consider storing a hash of *all* potentially interesting Shibboleth * attribute key/value pairs, not just the Identity Provider (IdP). @@ -389,6 +394,8 @@ public JsonObjectBuilder toJson() { authenicatedUserJson.add("deactivated", this.deactivated); authenicatedUserJson.add("deactivatedTime", UserUtil.getTimestampStringOrNull(this.deactivatedTime)); + authenicatedUserJson.add("mutedEmails", UserUtil.getMutedStringOrNull(this.mutedEmails)); + authenicatedUserJson.add("mutedNotifications", UserUtil.getMutedStringOrNull(this.mutedEmails)); return authenicatedUserJson; } @@ -492,4 +499,34 @@ public Cart getCart() { public void setCart(Cart cart) { this.cart = cart; } + + public Long getMutedEmails() { + return mutedEmails; + } + + public void setMutedEmails(Long mutedEmails) { + this.mutedEmails = mutedEmails; + } + + public Long getMutedNotifications() { + return mutedNotifications; + } + + public void setMutedNotifications(Long mutedNotifications) { + this.mutedNotifications = mutedNotifications; + } + + public boolean hasEmailMuted(UserNotification.Type type) { + if (this.mutedEmails == null || type == null) { + return false; + } + return (type.flagValue() & this.mutedEmails) > 0; + } + + public boolean hasNotificationMuted(UserNotification.Type type) { + if (this.mutedNotifications == null || type == null) { + return false; + } + return (type.flagValue() & this.mutedNotifications) > 0; + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java b/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java index 1ec17ac5928..d11a5a56400 100644 --- a/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java @@ -5,7 +5,11 @@ */ package edu.harvard.iq.dataverse.userdata; +import com.beust.jcommander.Strings; +import edu.harvard.iq.dataverse.UserNotification; import java.sql.Timestamp; +import java.util.List; +import java.util.stream.Collectors; /** * @@ -65,5 +69,19 @@ public static Timestamp getTimestampOrNull(Object dbResult){ } return (Timestamp)dbResult; } + + /** + * Convenience method to format UserNotification.Type from flag value + * @param mutedLongFlags + * @return + */ + public static String getMutedStringOrNull(Long mutedLongFlags){ + if (mutedLongFlags == null){ + return null; + } + final List types = UserNotification.Type.fromFlag(mutedLongFlags) + .stream().map(x -> x.name()).collect(Collectors.toList()); + return Strings.join(",", types); + } } diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 4c0938ee33f..4dcf2693eee 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -218,6 +218,35 @@ notification.workflowFailed=An external workflow run on {0} in {1} has failed. C notification.workflowSucceeded=An external workflow run on {0} in {1} has succeeded. Check your email and/or view the Dataset page which may have additional details. notification.statusUpdated=The status of dataset {0} has been updated to {1}. +# set this to false to disable muting options: +notification.showMuteOptions=true +notification.mutedEmails=Muted email notification types +notification.mutedNotifications=Muted notification types +notification.typeDescription.ASSIGNROLE=role is assigned +notification.typeDescription.REVOKEROLE=role is revoked +notification.typeDescription.CREATEDV=dataverse is created +notification.typeDescription.CREATEDS=your dataset is created +notification.typeDescription.CREATEACC=account is created +notification.typeDescription.SUBMITTEDDS=submitted for review +notification.typeDescription.RETURNEDDS=returned from review +notification.typeDescription.PUBLISHEDDS=dataset is published +notification.typeDescription.REQUESTFILEACCESS=access to file is requested +notification.typeDescription.GRANTFILEACCESS=access to file is granted +notification.typeDescription.REJECTFILEACCES=access to file is rejected +notification.typeDescription.FILESYSTEMIMPORT=dataset has been successfully uploaded and verified +notification.typeDescription.CHECKSUMIMPORT=dataset had file checksums added via a batch job +notification.typeDescription.CHECKSUMFAIL=checksum validation failed +# it could be counter-productive mute this one (email could not be verified), it is skipped by leaving it empty: +notification.typeDescription.CONFIRMEMAIL= +notification.typeDescription.APIGENERATED=API token is generated +notification.typeDescription.INGESTCOMPLETED=ingest is completed +notification.typeDescription.INGESTCOMPLETEDWITHERRORS=ingest completed with errors +notification.typeDescription.PUBLISHFAILED_PIDREG=publish has failed +notification.typeDescription.WORKFLOW_SUCCESS=external workflow run has succeeded +notification.typeDescription.WORKFLOW_FAILURE=external workflow run has failed +notification.typeDescription.STATUSUPDATED=status of dataset has been updated +notification.typeDescription.DATASETCREATED=dataset was created by user + notification.ingestCompleted=Dataset {1} has one or more tabular files that completed the tabular ingest process and are available in archival formats. notification.ingestCompletedWithErrors=Dataset {1} has one or more tabular files that are available but are not supported for tabular ingest. notification.generic.objectDeleted=The dataverse, dataset, or file for this notification has been deleted. diff --git a/src/main/resources/db/migration/V5.10.0.1__7492-muting-notifications.sql b/src/main/resources/db/migration/V5.10.0.1__7492-muting-notifications.sql new file mode 100644 index 00000000000..4afbd5abe3c --- /dev/null +++ b/src/main/resources/db/migration/V5.10.0.1__7492-muting-notifications.sql @@ -0,0 +1,5 @@ +ALTER TABLE authenticateduser +ADD COLUMN IF NOT EXISTS mutedemails BIGINT; + +ALTER TABLE authenticateduser +ADD COLUMN IF NOT EXISTS mutednotifications BIGINT; diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index cb922a0164d..0699e17d89b 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -377,6 +377,33 @@ +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+ +
From 99b47e03d26d69961cda0401d8412912a9678b18 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 29 Mar 2022 11:10:51 +0200 Subject: [PATCH 02/45] added tests to AuthenticatedUserTest.java --- .../users/AuthenticatedUserTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java b/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java index a756d7cd69e..23e4bb6b91b 100644 --- a/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java @@ -6,16 +6,20 @@ package edu.harvard.iq.dataverse.authorization.users; import edu.harvard.iq.dataverse.DatasetLock; +import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserLookup; import edu.harvard.iq.dataverse.mocks.MocksFactory; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Date; import java.util.List; import org.junit.Test; import static org.junit.Assert.*; import org.junit.Before; +import javax.json.JsonObject; + /** * Tested class: AuthenticatedUser.java * @@ -30,6 +34,7 @@ public AuthenticatedUserTest() { public static Timestamp expResult; public static Timestamp loginTime = Timestamp.valueOf("2000-01-01 00:00:00.0"); public static final String IDENTIFIER_PREFIX = "@"; + public static final List mutedTypes = Arrays.asList(UserNotification.Type.ASSIGNROLE, UserNotification.Type.REVOKEROLE); @Before public void setUp() { @@ -320,6 +325,58 @@ public void testHashCode() { int result = instance.hashCode(); assertEquals(expResult, result); } + + @Test + public void testMutingEmails() { + long mutedTypesFlags = UserNotification.Type.toFlag(mutedTypes); + System.out.println("setMutedEmails"); + testUser.setMutedEmails(mutedTypesFlags); + assertEquals(mutedTypesFlags, testUser.getMutedEmails().longValue()); + assertEquals(mutedTypes, UserNotification.Type.fromFlag(testUser.getMutedEmails())); + } + + @Test + public void testMutingNotifications() { + long mutedTypesFlags = UserNotification.Type.toFlag(mutedTypes); + System.out.println("setMutedNotifications"); + testUser.setMutedNotifications(mutedTypesFlags); + assertEquals(mutedTypesFlags, testUser.getMutedNotifications().longValue()); + assertEquals(mutedTypes, UserNotification.Type.fromFlag(testUser.getMutedNotifications())); + } + + @Test + public void testMutingInJson() { + long mutedTypesFlags = UserNotification.Type.toFlag(mutedTypes); + testUser.setMutedEmails(mutedTypesFlags); + testUser.setMutedNotifications(mutedTypesFlags); + System.out.println("toJson"); + JsonObject jObject = testUser.toJson().build(); + assertEquals("ASSIGNROLE,REVOKEROLE", jObject.getString("mutedEmails")); + assertEquals("ASSIGNROLE,REVOKEROLE", jObject.getString("mutedNotifications")); + } + + @Test + public void testHasEmailMuted() { + long mutedTypesFlags = UserNotification.Type.toFlag(mutedTypes); + testUser.setMutedEmails(mutedTypesFlags); + System.out.println("hasEmailMuted"); + assertEquals(true, testUser.hasEmailMuted(UserNotification.Type.ASSIGNROLE)); + assertEquals(true, testUser.hasEmailMuted(UserNotification.Type.REVOKEROLE)); + assertEquals(false, testUser.hasEmailMuted(UserNotification.Type.CREATEDV)); + assertEquals(false, testUser.hasEmailMuted(null)); + } + + @Test + public void testHasNotificationsMutedMuted() { + long mutedTypesFlags = UserNotification.Type.toFlag(mutedTypes); + testUser.setMutedNotifications(mutedTypesFlags); + System.out.println("hasNotificationMuted"); + assertEquals(true, testUser.hasNotificationMuted(UserNotification.Type.ASSIGNROLE)); + assertEquals(true, testUser.hasNotificationMuted(UserNotification.Type.REVOKEROLE)); + assertEquals(false, testUser.hasNotificationMuted(UserNotification.Type.CREATEDV)); + assertEquals(false, testUser.hasNotificationMuted(null)); + } + /** * All commented tests below have only been generated / are not complete for * AuthenticatedUser.java The tests above should all run fine, due to time From 234143569574412cb860c0064c7dd30dec567442 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 29 Mar 2022 12:05:39 +0200 Subject: [PATCH 03/45] muting properties are now better documented and moved to more logical position in bundle.properties --- src/main/java/propertyFiles/Bundle.properties | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 4dcf2693eee..cfb267108a4 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -218,10 +218,33 @@ notification.workflowFailed=An external workflow run on {0} in {1} has failed. C notification.workflowSucceeded=An external workflow run on {0} in {1} has succeeded. Check your email and/or view the Dataset page which may have additional details. notification.statusUpdated=The status of dataset {0} has been updated to {1}. -# set this to false to disable muting options: +notification.ingestCompleted=Dataset {1} has one or more tabular files that completed the tabular ingest process and are available in archival formats. +notification.ingestCompletedWithErrors=Dataset {1} has one or more tabular files that are available but are not supported for tabular ingest. +notification.generic.objectDeleted=The dataverse, dataset, or file for this notification has been deleted. +notification.access.granted.dataverse=You have been granted the {0} role for {1}. +notification.access.granted.dataset=You have been granted the {0} role for {1}. +notification.access.granted.datafile=You have been granted the {0} role for file in {1}. +notification.access.granted.fileDownloader.additionalDataverse={0} You now have access to all published restricted and unrestricted files in this dataverse. +notification.access.granted.fileDownloader.additionalDataset={0} You now have access to all published restricted and unrestricted files in this dataset. +notification.access.revoked.dataverse=You have been removed from a role in {0}. +notification.access.revoked.dataset=You have been removed from a role in {0}. +notification.access.revoked.datafile=You have been removed from a role in {0}. +notification.checksumfail=One or more files in your upload failed checksum validation for dataset {1}. Please re-run the upload script. If the problem persists, please contact support. +notification.ingest.completed=Your Dataset {2} has one or more tabular files that completed the tabular ingest process. These files will be available for download in their original formats and other formats for enhanced archival purposes after you publish the dataset. The archival .tab files are displayed in the file table. Please see the guides for more information about ingest and support for tabular files. +notification.ingest.completedwitherrors=Your Dataset {2} has one or more tabular files that have been uploaded successfully but are not supported for tabular ingest. After you publish the dataset, these files will not have additional archival features. Please see the guides for more information about ingest and support for tabular files.

Files with incomplete ingest:{5} +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 + +# Set notification.showMuteOptions to false to disable muting options: notification.showMuteOptions=true + +# These are the labels of the comboboxes where the muted notifications can be selected by the users notification.mutedEmails=Muted email notification types notification.mutedNotifications=Muted notification types + +# Notification types descriptions as presentend to the user. Leave the description empty or leave it out entirely in order to make it not selectable for muting by the user. notification.typeDescription.ASSIGNROLE=role is assigned notification.typeDescription.REVOKEROLE=role is revoked notification.typeDescription.CREATEDV=dataverse is created @@ -236,7 +259,7 @@ notification.typeDescription.REJECTFILEACCES=access to file is rejected notification.typeDescription.FILESYSTEMIMPORT=dataset has been successfully uploaded and verified notification.typeDescription.CHECKSUMIMPORT=dataset had file checksums added via a batch job notification.typeDescription.CHECKSUMFAIL=checksum validation failed -# it could be counter-productive mute this one (email could not be verified), it is skipped by leaving it empty: +# It could be counter-productive to mute this one (email could not be verified), it is skipped (not selectable by the user) by leaving the description empty: notification.typeDescription.CONFIRMEMAIL= notification.typeDescription.APIGENERATED=API token is generated notification.typeDescription.INGESTCOMPLETED=ingest is completed @@ -247,24 +270,6 @@ notification.typeDescription.WORKFLOW_FAILURE=external workflow run has failed notification.typeDescription.STATUSUPDATED=status of dataset has been updated notification.typeDescription.DATASETCREATED=dataset was created by user -notification.ingestCompleted=Dataset {1} has one or more tabular files that completed the tabular ingest process and are available in archival formats. -notification.ingestCompletedWithErrors=Dataset {1} has one or more tabular files that are available but are not supported for tabular ingest. -notification.generic.objectDeleted=The dataverse, dataset, or file for this notification has been deleted. -notification.access.granted.dataverse=You have been granted the {0} role for {1}. -notification.access.granted.dataset=You have been granted the {0} role for {1}. -notification.access.granted.datafile=You have been granted the {0} role for file in {1}. -notification.access.granted.fileDownloader.additionalDataverse={0} You now have access to all published restricted and unrestricted files in this dataverse. -notification.access.granted.fileDownloader.additionalDataset={0} You now have access to all published restricted and unrestricted files in this dataset. -notification.access.revoked.dataverse=You have been removed from a role in {0}. -notification.access.revoked.dataset=You have been removed from a role in {0}. -notification.access.revoked.datafile=You have been removed from a role in {0}. -notification.checksumfail=One or more files in your upload failed checksum validation for dataset {1}. Please re-run the upload script. If the problem persists, please contact support. -notification.ingest.completed=Your Dataset {2} has one or more tabular files that completed the tabular ingest process. These files will be available for download in their original formats and other formats for enhanced archival purposes after you publish the dataset. The archival .tab files are displayed in the file table. Please see the guides for more information about ingest and support for tabular files. -notification.ingest.completedwitherrors=Your Dataset {2} has one or more tabular files that have been uploaded successfully but are not supported for tabular ingest. After you publish the dataset, these files will not have additional archival features. Please see the guides for more information about ingest and support for tabular files.

Files with incomplete ingest:{5} -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 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. From 9b79afb90c4186e7b14b1ade6d5cbc93e2b8f84f Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 29 Mar 2022 15:36:52 +0200 Subject: [PATCH 04/45] added support for muting notification types for the installation where users cannot unmuted these muted types --- .../harvard/iq/dataverse/UserNotification.java | 15 ++++++++++++++- .../iq/dataverse/UserNotificationServiceBean.java | 7 +++++-- .../providers/builtin/DataverseUserPage.java | 3 ++- src/main/java/propertyFiles/Bundle.properties | 3 +++ .../users/AuthenticatedUserTest.java | 11 +++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java index 72622b11942..6ab28d6db9a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java @@ -9,6 +9,10 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.Collections; +import java.util.stream.Collectors; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Enumerated; @@ -44,7 +48,7 @@ public long flagValue() { } public static List fromFlag(Long flag) { - final List types = new ArrayList(); + final List types = new ArrayList(); if (flag == null) { return types; } @@ -66,6 +70,15 @@ public static Long toFlag(final List types) { } return flag; } + + public static Set tokenizeToSet(String tokens) { + if (tokens == null || tokens.isEmpty()) { + return Collections.emptySet(); + } + return Collections.list(new StringTokenizer(tokens, ",")).stream() + .map(token -> Type.valueOf(((String) token).trim())) + .collect(Collectors.toSet()); + } }; private static final long serialVersionUID = 1L; diff --git a/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java index 610b2f49e87..cf4aeec51c8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserNotificationServiceBean.java @@ -8,8 +8,10 @@ import edu.harvard.iq.dataverse.UserNotification.Type; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.util.BundleUtil; import java.sql.Timestamp; import java.util.List; +import java.util.Set; import java.util.logging.Logger; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -103,6 +105,7 @@ public void sendNotification(AuthenticatedUser dataverseUser, Timestamp sendDate } public void sendNotification(AuthenticatedUser dataverseUser, Timestamp sendDate, Type type, Long objectId, String comment, AuthenticatedUser requestor, boolean isHtmlContent) { + final Set alwaysMuted = Type.tokenizeToSet(BundleUtil.getStringFromBundle("notification.alwaysMuted")); UserNotification userNotification = new UserNotification(); userNotification.setUser(dataverseUser); userNotification.setSendDate(sendDate); @@ -110,13 +113,13 @@ public void sendNotification(AuthenticatedUser dataverseUser, Timestamp sendDate userNotification.setObjectId(objectId); userNotification.setRequestor(requestor); - if (!isEmailMuted(userNotification) && mailService.sendNotificationEmail(userNotification, comment, requestor, isHtmlContent)) { + if (!alwaysMuted.contains(userNotification.getType()) && !isEmailMuted(userNotification) && mailService.sendNotificationEmail(userNotification, comment, requestor, isHtmlContent)) { logger.fine("email was sent"); userNotification.setEmailed(true); } else { logger.fine("email was not sent"); } - if (!isNotificationMuted(userNotification)) { + if (!alwaysMuted.contains(userNotification.getType()) && !isNotificationMuted(userNotification)) { save(userNotification); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 4c5122c2a7d..172607bb988 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -167,8 +167,9 @@ public String init() { setCurrentUser((AuthenticatedUser) session.getUser()); userAuthProvider = authenticationService.lookupProvider(currentUser); notificationsList = userNotificationService.findByUser(currentUser.getId()); + final Set alwaysMuted = UserNotification.Type.tokenizeToSet(BundleUtil.getStringFromBundle("notification.alwaysMuted")); notificationTypeList = Arrays.asList(UserNotification.Type.values()).stream() - .filter(x -> x.getDescription() != null && !x.getDescription().isEmpty()) + .filter(x -> !alwaysMuted.contains(x) && x.getDescription() != null && !x.getDescription().isEmpty()) .collect(Collectors.toList()); mutedEmailList = UserNotification.Type.fromFlag(currentUser.getMutedEmails()); mutedNotificationList = UserNotification.Type.fromFlag(currentUser.getMutedNotifications()); diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index cfb267108a4..305f7162ed5 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -240,6 +240,9 @@ removeNotification=Remove Notification # Set notification.showMuteOptions to false to disable muting options: notification.showMuteOptions=true +# List (comma separated) of always muted notifications that cannot be turned on by the users: +# notification.alwaysMuted=ASSIGNROLE, REVOKEROLE + # These are the labels of the comboboxes where the muted notifications can be selected by the users notification.mutedEmails=Muted email notification types notification.mutedNotifications=Muted notification types diff --git a/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java b/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java index 23e4bb6b91b..4afcc9ecbb5 100644 --- a/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUserTest.java @@ -14,6 +14,8 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Set; + import org.junit.Test; import static org.junit.Assert.*; import org.junit.Before; @@ -377,6 +379,15 @@ public void testHasNotificationsMutedMuted() { assertEquals(false, testUser.hasNotificationMuted(null)); } + @Test + public void testTypeTokenizer() { + final Set typeSet = UserNotification.Type.tokenizeToSet(" ASSIGNROLE , CREATEDV,REVOKEROLE "); + assertTrue("typeSet contains 3 elements", typeSet.size() == 3); + assertTrue("typeSet contains ASSIGNROLE", typeSet.contains(UserNotification.Type.ASSIGNROLE)); + assertTrue("typeSet contains CREATEDV", typeSet.contains(UserNotification.Type.CREATEDV)); + assertTrue("typeSet contains REVOKEROLE", typeSet.contains(UserNotification.Type.REVOKEROLE)); + } + /** * All commented tests below have only been generated / are not complete for * AuthenticatedUser.java The tests above should all run fine, due to time From abe1af23a84ca0e4dfb9e004c133827ebb2a5276 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Wed, 30 Mar 2022 18:21:10 +0200 Subject: [PATCH 05/45] implementation of in review requested changes --- .../harvard/iq/dataverse/SettingsWrapper.java | 51 +++++++++++++++---- .../iq/dataverse/UserNotification.java | 8 ++- .../providers/builtin/DataverseUserPage.java | 35 ++++++------- .../users/AuthenticatedUser.java | 46 ++++++++++++----- .../settings/SettingsServiceBean.java | 14 ++++- .../iq/dataverse/userdata/UserUtil.java | 12 ++--- src/main/java/propertyFiles/Bundle.properties | 6 --- src/main/webapp/dataverseuser.xhtml | 2 +- .../users/AuthenticatedUserTest.java | 42 ++++++++------- 9 files changed, 141 insertions(+), 75 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java b/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java index 7c4569e1dc5..6a2b70b6334 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java +++ b/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java @@ -13,18 +13,18 @@ import edu.harvard.iq.dataverse.util.MailUtil; import edu.harvard.iq.dataverse.util.StringUtil; import edu.harvard.iq.dataverse.util.SystemConfig; +import edu.harvard.iq.dataverse.UserNotification.Type; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.logging.Logger; +import java.util.Set; import javax.ejb.EJB; import javax.faces.application.FacesMessage; @@ -34,15 +34,8 @@ import javax.faces.validator.ValidatorException; import javax.faces.view.ViewScoped; import javax.inject.Named; -import javax.json.Json; -import javax.json.JsonArray; import javax.json.JsonObject; -import javax.json.JsonReader; -import javax.json.JsonValue; import javax.mail.internet.InternetAddress; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; /** * @@ -111,6 +104,10 @@ public class SettingsWrapper implements java.io.Serializable { private Boolean customLicenseAllowed = null; + private Set alwaysMuted = null; + + private Set neverMuted = null; + public String get(String settingKey) { if (settingsMap == null) { initSettingsMap(); @@ -185,6 +182,42 @@ private void initSettingsMap() { } } + private void initAlwyasMuted() { + alwaysMuted = UserNotification.Type.tokenizeToSet(getValueForKey(Key.AlwaysMuted)); + } + + private void initNeverMuted() { + neverMuted = UserNotification.Type.tokenizeToSet(getValueForKey(Key.NeverMuted)); + } + + private Set getAlwaysMutedSet() { + if (alwaysMuted == null) { + initAlwyasMuted(); + } + return alwaysMuted; + } + + private Set getNeverMutedSet() { + if (neverMuted == null) { + initNeverMuted(); + } + return neverMuted; + } + + public boolean isAlwaysMuted(Type type) { + return getAlwaysMutedSet().contains(type); + } + + public boolean isNeverMuted(Type type) { + return getNeverMutedSet().contains(type); + } + + public String getShowMuteOptions() { + final boolean safeDefaultIfKeyNotFound = false; + final boolean doShow = isTrueForKey(Key.ShowMuteOptions, safeDefaultIfKeyNotFound); + return doShow ? "TRUE" : "FALSE"; + } + public String getGuidesBaseUrl() { if (guidesBaseUrl == null) { diff --git a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java index 6ab28d6db9a..2a6e60be567 100644 --- a/src/main/java/edu/harvard/iq/dataverse/UserNotification.java +++ b/src/main/java/edu/harvard/iq/dataverse/UserNotification.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.Collection; import java.util.StringTokenizer; import java.util.Collections; import java.util.stream.Collectors; @@ -42,6 +43,11 @@ public enum Type { public String getDescription() { return BundleUtil.getStringFromBundle("notification.typeDescription." + this.name()); } + + public boolean hasDescription() { + final String description = getDescription(); + return description != null && !description.isEmpty(); + } public long flagValue() { return 1 << this.ordinal(); @@ -60,7 +66,7 @@ public static List fromFlag(Long flag) { return types; } - public static Long toFlag(final List types) { + public static Long toFlag(final Collection types) { if (types == null || types.isEmpty()) { return null; } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 172607bb988..880ef1e4ded 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -17,6 +17,7 @@ import edu.harvard.iq.dataverse.SettingsWrapper; import edu.harvard.iq.dataverse.UserNameValidator; import edu.harvard.iq.dataverse.UserNotification; +import edu.harvard.iq.dataverse.UserNotification.Type; import edu.harvard.iq.dataverse.UserNotificationServiceBean; import edu.harvard.iq.dataverse.UserServiceBean; import edu.harvard.iq.dataverse.authorization.AuthUtil; @@ -46,6 +47,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.EnumSet; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -137,9 +139,9 @@ public enum EditMode { private List passwordErrors; - private List notificationTypeList; - private List mutedEmailList; - private List mutedNotificationList; + private List notificationTypeList; + private List mutedEmailList; + private List mutedNotificationList; public String init() { @@ -167,12 +169,11 @@ public String init() { setCurrentUser((AuthenticatedUser) session.getUser()); userAuthProvider = authenticationService.lookupProvider(currentUser); notificationsList = userNotificationService.findByUser(currentUser.getId()); - final Set alwaysMuted = UserNotification.Type.tokenizeToSet(BundleUtil.getStringFromBundle("notification.alwaysMuted")); - notificationTypeList = Arrays.asList(UserNotification.Type.values()).stream() - .filter(x -> !alwaysMuted.contains(x) && x.getDescription() != null && !x.getDescription().isEmpty()) + notificationTypeList = Arrays.asList(Type.values()).stream() + .filter(x -> !settingsWrapper.isAlwaysMuted(x) && !settingsWrapper.isAlwaysMuted(x) && x.hasDescription()) .collect(Collectors.toList()); - mutedEmailList = UserNotification.Type.fromFlag(currentUser.getMutedEmails()); - mutedNotificationList = UserNotification.Type.fromFlag(currentUser.getMutedNotifications()); + mutedEmailList = new ArrayList<>(currentUser.getMutedEmailsSet()); + mutedNotificationList = new ArrayList<>(currentUser.getMutedNotificationsSet()); switch (selectTab) { case "notifications": @@ -346,7 +347,7 @@ public String save() { */ userNotificationService.sendNotification(au, new Timestamp(new Date().getTime()), - UserNotification.Type.CREATEACC, null); + Type.CREATEACC, null); // go back to where user came from @@ -380,8 +381,8 @@ public String save() { logger.info("Redirecting"); return permissionsWrapper.notAuthorized() + "faces-redirect=true"; }else { - currentUser.setMutedEmails(UserNotification.Type.toFlag(mutedEmailList)); - currentUser.setMutedNotifications(UserNotification.Type.toFlag(mutedNotificationList)); + currentUser.setMutedEmailsSet(EnumSet.copyOf(mutedEmailList)); + currentUser.setMutedNotificationsSet(EnumSet.copyOf(mutedNotificationList)); String emailBeforeUpdate = currentUser.getEmail(); AuthenticatedUser savedUser = authenticationService.updateAuthenticatedUser(currentUser, userDisplayInfo); String emailAfterUpdate = savedUser.getEmail(); @@ -723,27 +724,27 @@ public String getRequestorEmail(UserNotification notification) { return notification.getRequestor().getEmail() != null ? notification.getRequestor().getEmail() : BundleUtil.getStringFromBundle("notification.email.info.unavailable"); } - public List getNotificationTypeList() { + public List getNotificationTypeList() { return notificationTypeList; } - public void setNotificationTypeList(List notificationTypeList) { + public void setNotificationTypeList(List notificationTypeList) { this.notificationTypeList = notificationTypeList; } - public List getMutedEmailList() { + public List getMutedEmailList() { return mutedEmailList; } - public void setMutedEmailList(List mutedEmailList) { + public void setMutedEmailList(List mutedEmailList) { this.mutedEmailList = mutedEmailList; } - public List getMutedNotificationList() { + public List getMutedNotificationList() { return mutedNotificationList; } - public void setMutedNotificationList(List mutedNotificationList) { + public void setMutedNotificationList(List mutedNotificationList) { this.mutedNotificationList = mutedNotificationList; } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index ccad45c87ff..0f31134062f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.Cart; import edu.harvard.iq.dataverse.DatasetLock; import edu.harvard.iq.dataverse.UserNotification; +import edu.harvard.iq.dataverse.UserNotification.Type; import edu.harvard.iq.dataverse.ValidateEmail; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserLookup; @@ -14,8 +15,11 @@ import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import java.io.Serializable; import java.sql.Timestamp; +import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.Set; + import javax.json.Json; import javax.json.JsonObjectBuilder; import javax.persistence.CascadeType; @@ -125,6 +129,12 @@ public class AuthenticatedUser implements User, Serializable { @Column(nullable=true) private Long mutedNotifications; + + @Transient + private Set mutedEmailsSet; + + @Transient + private Set mutedNotificationsSet; /** * @todo Consider storing a hash of *all* potentially interesting Shibboleth @@ -394,8 +404,8 @@ public JsonObjectBuilder toJson() { authenicatedUserJson.add("deactivated", this.deactivated); authenicatedUserJson.add("deactivatedTime", UserUtil.getTimestampStringOrNull(this.deactivatedTime)); - authenicatedUserJson.add("mutedEmails", UserUtil.getMutedStringOrNull(this.mutedEmails)); - authenicatedUserJson.add("mutedNotifications", UserUtil.getMutedStringOrNull(this.mutedEmails)); + authenicatedUserJson.add("mutedEmails", UserUtil.getMutedStringOrNull(this.mutedEmailsSet)); + authenicatedUserJson.add("mutedNotifications", UserUtil.getMutedStringOrNull(this.mutedEmailsSet)); return authenicatedUserJson; } @@ -500,33 +510,45 @@ public void setCart(Cart cart) { this.cart = cart; } - public Long getMutedEmails() { - return mutedEmails; + public Set getMutedEmailsSet() { + return mutedEmailsSet; } public void setMutedEmails(Long mutedEmails) { this.mutedEmails = mutedEmails; + this.mutedEmailsSet = EnumSet.copyOf(Type.fromFlag(mutedEmails)); } - public Long getMutedNotifications() { - return mutedNotifications; + public void setMutedEmailsSet(Set mutedEmails) { + this.mutedEmailsSet = mutedEmails; + this.mutedEmails = Type.toFlag(mutedEmails); + } + + public Set getMutedNotificationsSet() { + return mutedNotificationsSet; } public void setMutedNotifications(Long mutedNotifications) { this.mutedNotifications = mutedNotifications; + this.mutedNotificationsSet = EnumSet.copyOf(Type.fromFlag(mutedNotifications)); + } + + public void setMutedNotificationsSet(Set mutedNotifications) { + this.mutedNotificationsSet = mutedNotifications; + this.mutedNotifications = Type.toFlag(mutedNotifications); } - public boolean hasEmailMuted(UserNotification.Type type) { - if (this.mutedEmails == null || type == null) { + public boolean hasEmailMuted(Type type) { + if (this.mutedEmailsSet == null || type == null) { return false; } - return (type.flagValue() & this.mutedEmails) > 0; + return this.mutedEmailsSet.contains(type); } - public boolean hasNotificationMuted(UserNotification.Type type) { - if (this.mutedNotifications == null || type == null) { + public boolean hasNotificationMuted(Type type) { + if (this.mutedNotificationsSet == null || type == null) { return false; } - return (type.flagValue() & this.mutedNotifications) > 0; + return this.mutedNotificationsSet.contains(type); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index e13ea806dc7..1c1d6bd41a5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -505,7 +505,19 @@ Whether Harvesting (OAI) service is enabled /* * Include "Custom Terms" as an item in the license drop-down or not. */ - AllowCustomTermsOfUse + AllowCustomTermsOfUse, + /* + * Allow users to mute notifications or not. + */ + ShowMuteOptions, + /* + * List (comma separated, e.g., "ASSIGNROLE, REVOKEROLE") of always muted notifications that cannot be turned on by the users. + */ + AlwaysMuted, + /* + * List (comma separated, e.g., "ASSIGNROLE, REVOKEROLE") of never muted notifications that cannot be turned off by the users. + */ + NeverMuted ; @Override diff --git a/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java b/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java index d11a5a56400..8b655d9bd8f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/userdata/UserUtil.java @@ -5,10 +5,10 @@ */ package edu.harvard.iq.dataverse.userdata; -import com.beust.jcommander.Strings; -import edu.harvard.iq.dataverse.UserNotification; +import edu.harvard.iq.dataverse.UserNotification.Type; import java.sql.Timestamp; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -75,13 +75,13 @@ public static Timestamp getTimestampOrNull(Object dbResult){ * @param mutedLongFlags * @return */ - public static String getMutedStringOrNull(Long mutedLongFlags){ - if (mutedLongFlags == null){ + public static String getMutedStringOrNull(Set muted){ + if (muted == null){ return null; } - final List types = UserNotification.Type.fromFlag(mutedLongFlags) + final List types = muted .stream().map(x -> x.name()).collect(Collectors.toList()); - return Strings.join(",", types); + return String.join(",", types); } } diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 305f7162ed5..d9955f2e791 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -237,12 +237,6 @@ notification.import.filesystem=Dataset {1}, dataset had file checksums added via a batch job. removeNotification=Remove Notification -# Set notification.showMuteOptions to false to disable muting options: -notification.showMuteOptions=true - -# List (comma separated) of always muted notifications that cannot be turned on by the users: -# notification.alwaysMuted=ASSIGNROLE, REVOKEROLE - # These are the labels of the comboboxes where the muted notifications can be selected by the users notification.mutedEmails=Muted email notification types notification.mutedNotifications=Muted notification types diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 0699e17d89b..9ad475c7079 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -377,7 +377,7 @@
-
+

- - - + + +
@@ -395,10 +394,9 @@ #{bundle['notification.mutedNotifications']}
- - - + + +
Date: Tue, 19 Apr 2022 14:13:07 +0200 Subject: [PATCH 11/45] removed unused import --- .../authorization/providers/builtin/DataverseUserPage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 5dcdc919d4c..850f14d6ce3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -64,7 +64,6 @@ import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.constraints.NotBlank; import org.primefaces.event.TabChangeEvent; -import javax.faces.event.AjaxBehaviorEvent; /** * From 70e01d93513e2f1621016fbd59657062e5f1d4c4 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 19 Apr 2022 14:14:12 +0200 Subject: [PATCH 12/45] resolved merge conflict --- .../iq/dataverse/authorization/users/AuthenticatedUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index 8d4ab1432fb..c250556f5dc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -4,7 +4,7 @@ import edu.harvard.iq.dataverse.DatasetLock; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.UserNotification.Type; -import edu.harvard.iq.dataverse.ValidateEmail; +import edu.harvard.iq.dataverse.validation.ValidateEmail; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserLookup; import edu.harvard.iq.dataverse.authorization.providers.oauth2.OAuth2TokenData; From 4fd7e53bd1bf51e4f15dfbaa77763433392137f5 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 19 Apr 2022 14:21:31 +0200 Subject: [PATCH 13/45] renames of the notification variables in AuthenticatedUser --- .../providers/builtin/DataverseUserPage.java | 32 +++++++++---------- src/main/webapp/dataverseuser.xhtml | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index 850f14d6ce3..c158011739c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -141,8 +141,8 @@ public enum EditMode { private List notificationTypeList; - private Set mutedEmailList; - private Set mutedNotificationList; + private Set mutedEmails; + private Set mutedNotifications; private Set disabledNotifications; public String init() { @@ -174,10 +174,10 @@ public String init() { notificationTypeList = Arrays.asList(Type.values()).stream() .filter(x -> !Type.CONFIRMEMAIL.equals(x) && x.hasDescription()) .collect(Collectors.toList()); - mutedEmailList = new HashSet<>(currentUser.getMutedEmails()); - mutedEmailList.addAll(settingsWrapper.getAlwaysMutedSet()); - mutedNotificationList = new HashSet<>(currentUser.getMutedNotifications()); - mutedNotificationList.addAll(settingsWrapper.getAlwaysMutedSet()); + mutedEmails = new HashSet<>(currentUser.getMutedEmails()); + mutedEmails.addAll(settingsWrapper.getAlwaysMutedSet()); + mutedNotifications = new HashSet<>(currentUser.getMutedNotifications()); + mutedNotifications.addAll(settingsWrapper.getAlwaysMutedSet()); disabledNotifications = new HashSet<>(settingsWrapper.getAlwaysMutedSet()); disabledNotifications.addAll(settingsWrapper.getNeverMutedSet()); @@ -387,8 +387,8 @@ public String save() { logger.info("Redirecting"); return permissionsWrapper.notAuthorized() + "faces-redirect=true"; }else { - currentUser.setMutedEmails(mutedEmailList); - currentUser.setMutedNotifications(mutedNotificationList); + currentUser.setMutedEmails(mutedEmails); + currentUser.setMutedNotifications(mutedNotifications); String emailBeforeUpdate = currentUser.getEmail(); AuthenticatedUser savedUser = authenticationService.updateAuthenticatedUser(currentUser, userDisplayInfo); String emailAfterUpdate = savedUser.getEmail(); @@ -738,20 +738,20 @@ public void setNotificationTypeList(List notificationTypeList) { this.notificationTypeList = notificationTypeList; } - public Set getMutedEmailList() { - return mutedEmailList; + public Set getMutedEmails() { + return mutedEmails; } - public void setMutedEmailList(Set mutedEmailList) { - this.mutedEmailList = mutedEmailList; + public void setMutedEmails(Set mutedEmails) { + this.mutedEmails = mutedEmails; } - public Set getMutedNotificationList() { - return mutedNotificationList; + public Set getMutedNotifications() { + return mutedNotifications; } - public void setMutedNotificationList(Set mutedNotificationList) { - this.mutedNotificationList = mutedNotificationList; + public void setMutedNotifications(Set mutedNotifications) { + this.mutedNotifications = mutedNotifications; } public boolean isDisabled(Type t) { diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 875c93190a1..91e852d5ccf 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -384,7 +384,7 @@ #{bundle['notification.mutedEmails']}
- +
@@ -394,7 +394,7 @@ #{bundle['notification.mutedNotifications']}
- +
From 8d2e3ce517f279115c8941ae5edc204afac42329 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 19 Apr 2022 14:28:01 +0200 Subject: [PATCH 14/45] resolved merge conflict --- .../iq/dataverse/authorization/users/AuthenticatedUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index c250556f5dc..1706ed846f8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -2,8 +2,8 @@ import edu.harvard.iq.dataverse.Cart; import edu.harvard.iq.dataverse.DatasetLock; -import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.UserNotification.Type; +import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.validation.ValidateEmail; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticatedUserLookup; From cf13a0b8fd1698afa3d9e2909e76edc30d730192 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Mon, 25 Apr 2022 16:14:20 +0200 Subject: [PATCH 15/45] muting options are now collapsible and the logic is inverterd: you select notifications that you want to receive, not to muted --- .../providers/builtin/DataverseUserPage.java | 16 ++--- src/main/java/propertyFiles/Bundle.properties | 7 ++- src/main/webapp/dataverseuser.xhtml | 63 +++++++++++-------- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index fdf2ad546c9..ae01c22207b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -738,20 +738,20 @@ public void setNotificationTypeList(List notificationTypeList) { this.notificationTypeList = notificationTypeList; } - public Set getMutedEmails() { - return mutedEmails; + public Set getToReceiveEmails() { + return notificationTypeList.stream().filter(x -> !mutedEmails.contains(x)).collect(Collectors.toSet()); } - public void setMutedEmails(Set mutedEmails) { - this.mutedEmails = mutedEmails; + public void setToReceiveEmails(Set toReceiveEmails) { + this.mutedEmails = notificationTypeList.stream().filter(x -> !toReceiveEmails.contains(x)).collect(Collectors.toSet()); } - public Set getMutedNotifications() { - return mutedNotifications; + public Set getToReceiveNotifications() { + return notificationTypeList.stream().filter(x -> !mutedNotifications.contains(x)).collect(Collectors.toSet()); } - public void setMutedNotifications(Set mutedNotifications) { - this.mutedNotifications = mutedNotifications; + public void setToReceiveNotifications(Set toReceiveNotifications) { + this.mutedNotifications = notificationTypeList.stream().filter(x -> !toReceiveNotifications.contains(x)).collect(Collectors.toSet()); } public boolean isDisabled(Type t) { diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 567a63a248d..72d6fda8e64 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -237,9 +237,10 @@ notification.import.filesystem=Dataset {1}, dataset had file checksums added via a batch job. removeNotification=Remove Notification -# These are the labels of the comboboxes where the muted notifications can be selected by the users -notification.mutedEmails=Muted email notification types -notification.mutedNotifications=Muted notification types +# These are the labels of the options where the muted notifications can be selected by the users +notification.muteOptions=Notifications settings +notification.mutedEmails=Select the email notifications you wish to receive: +notification.mutedNotifications=Select the in-app notifications you wish to receive: # Notification types descriptions as presentend to the user. Leave the description empty or leave it out entirely in order to make it not selectable for muting by the user. notification.typeDescription.ASSIGNROLE=Role is assigned diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 91e852d5ccf..78553a83c5d 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -59,6 +59,44 @@ +
+ +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+ +
+
+
+
@@ -377,31 +415,6 @@
-
-
-
- -
- - - -
-
-
- -
- - - -
-
- -
From e5e80d88d7f2f5dbf82008a4135401bb53b6e14d Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Mon, 25 Apr 2022 17:03:09 +0200 Subject: [PATCH 16/45] bugfix in inverted muting logic --- .../providers/builtin/DataverseUserPage.java | 18 ++++++++++++------ src/main/java/propertyFiles/Bundle.properties | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java index ae01c22207b..93f84527298 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java @@ -175,9 +175,7 @@ public String init() { .filter(x -> !Type.CONFIRMEMAIL.equals(x) && x.hasDescription()) .collect(Collectors.toList()); mutedEmails = new HashSet<>(currentUser.getMutedEmails()); - mutedEmails.addAll(settingsWrapper.getAlwaysMutedSet()); mutedNotifications = new HashSet<>(currentUser.getMutedNotifications()); - mutedNotifications.addAll(settingsWrapper.getAlwaysMutedSet()); disabledNotifications = new HashSet<>(settingsWrapper.getAlwaysMutedSet()); disabledNotifications.addAll(settingsWrapper.getNeverMutedSet()); @@ -739,19 +737,27 @@ public void setNotificationTypeList(List notificationTypeList) { } public Set getToReceiveEmails() { - return notificationTypeList.stream().filter(x -> !mutedEmails.contains(x)).collect(Collectors.toSet()); + return notificationTypeList.stream().filter( + x -> isDisabled(x) ? !settingsWrapper.isAlwaysMuted(x) && settingsWrapper.isNeverMuted(x) : !mutedEmails.contains(x) + ).collect(Collectors.toSet()); } public void setToReceiveEmails(Set toReceiveEmails) { - this.mutedEmails = notificationTypeList.stream().filter(x -> !toReceiveEmails.contains(x)).collect(Collectors.toSet()); + this.mutedEmails = notificationTypeList.stream().filter( + x -> !isDisabled(x) && !toReceiveEmails.contains(x) + ).collect(Collectors.toSet()); } public Set getToReceiveNotifications() { - return notificationTypeList.stream().filter(x -> !mutedNotifications.contains(x)).collect(Collectors.toSet()); + return notificationTypeList.stream().filter( + x -> isDisabled(x) ? !settingsWrapper.isAlwaysMuted(x) && settingsWrapper.isNeverMuted(x) : !mutedNotifications.contains(x) + ).collect(Collectors.toSet()); } public void setToReceiveNotifications(Set toReceiveNotifications) { - this.mutedNotifications = notificationTypeList.stream().filter(x -> !toReceiveNotifications.contains(x)).collect(Collectors.toSet()); + this.mutedNotifications = notificationTypeList.stream().filter( + x -> !isDisabled(x) && !toReceiveNotifications.contains(x) + ).collect(Collectors.toSet()); } public boolean isDisabled(Type t) { diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index 72d6fda8e64..eba5e7e71e7 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -238,7 +238,7 @@ notification.import.checksum= + action="#{DataverseUserPage.save}" onclick="PF('muteCollapse').collapse('hide')"/>
From f2b26fe875ba4d8159136ca20f5058551e926913 Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Tue, 26 Apr 2022 12:23:04 +0200 Subject: [PATCH 18/45] when saving notification settings the settings panel collapses; added update property --- src/main/webapp/dataverseuser.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index d00553fbee7..1a10e04735a 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -92,7 +92,7 @@ + action="#{DataverseUserPage.save}" onclick="PF('muteCollapse').collapse('hide')" update="@form"/> From a83cfffaf04d22aa890198e4ddf2a27315e540ec Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Wed, 27 Apr 2022 10:52:34 +0200 Subject: [PATCH 19/45] fixed inconsistencies when switching tabs and improved accessibility --- src/main/webapp/dataverseuser.xhtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 1a10e04735a..97d00f7c326 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -53,14 +53,14 @@ - +
-