Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/release-notes/11670-notification-of-moved-datasets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Notifications

New notification was added for Datasets moving between Dataverses.
Requires SettingsServiceBean.Key.SendNotificationOnDatasetMove setting to be enabled.

See #11670
1 change: 1 addition & 0 deletions doc/sphinx-guides/source/admin/user-administration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ This enables additional settings for each user in the notifications tab of their
* ``CREATEDS`` Your dataset is created
* ``CREATEDV`` Dataverse collection is created
* ``DATASETCREATED`` Dataset was created by user
* ``DATASETMOVED`` Dataset was moved by user
* ``FILESYSTEMIMPORT`` Dataset has been successfully uploaded and verified
* ``GRANTFILEACCESS`` Access to file is granted
* ``INGESTCOMPLETEDWITHERRORS`` Ingest completed with errors
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio
String[] paramArrayDatasetCreated = {getDatasetLink(dataset), dataset.getDisplayName(), userNotification.getRequestor().getName(), dataset.getOwner().getDisplayName()};
messageText += MessageFormat.format(pattern, paramArrayDatasetCreated);
return messageText;
case DATASETMOVED:
dataset = (Dataset) targetObject;
pattern = BundleUtil.getStringFromBundle("notification.email.datasetWasMoved");
String[] paramArrayDatasetMoved = {getDatasetLink(dataset), dataset.getDisplayName(), userNotification.getRequestor().getName(), dataset.getOwner().getDisplayName()};
messageText += MessageFormat.format(pattern, paramArrayDatasetMoved);
return messageText;
case CREATEDS:
version = (DatasetVersion) targetObject;
String datasetCreatedMessage = BundleUtil.getStringFromBundle("notification.email.createDataset", Arrays.asList(
Expand Down Expand Up @@ -785,6 +791,7 @@ public Object getObjectOfNotification (UserNotification userNotification){
case GRANTFILEACCESS:
case REJECTFILEACCESS:
case DATASETCREATED:
case DATASETMOVED:
case DATASETMENTIONED:
return datasetService.find(userNotification.getObjectId());
case CREATEDS:
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/UserNotification.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public enum Type {
PUBLISHFAILED_PIDREG, WORKFLOW_SUCCESS, WORKFLOW_FAILURE, STATUSUPDATED, DATASETCREATED, DATASETMENTIONED,
GLOBUSUPLOADCOMPLETED, GLOBUSUPLOADCOMPLETEDWITHERRORS,
GLOBUSDOWNLOADCOMPLETED, GLOBUSDOWNLOADCOMPLETEDWITHERRORS, REQUESTEDFILEACCESS,
GLOBUSUPLOADREMOTEFAILURE, GLOBUSUPLOADLOCALFAILURE, PIDRECONCILED;
GLOBUSUPLOADREMOTEFAILURE, GLOBUSUPLOADLOCALFAILURE, PIDRECONCILED,
DATASETMOVED;

public String getDescription() {
return BundleUtil.getStringFromBundle("notification.typeDescription." + this.name());
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
Original file line number Diff line number Diff line change
Expand Up @@ -1396,8 +1396,7 @@ public Response moveDataset(@Context ContainerRequestContext crc, @PathParam("id
}
//Command requires Super user - it will be tested by the command
execCommand(new MoveDatasetCommand(
createDataverseRequest(u), ds, target, force
));
createDataverseRequest(u), ds, target, force, true));
return ok(BundleUtil.getStringFromBundle("datasets.api.moveDataset.success"));
} catch (WrappedResponse ex) {
if (ex.getCause() instanceof UnforcedCommandException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ public void displayNotification() {
case GRANTFILEACCESS:
case REJECTFILEACCESS:
case DATASETCREATED:
case DATASETMOVED:
case DATASETMENTIONED:
userNotification.setTheObject(datasetService.find(userNotification.getObjectId()));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ public void move(){
HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
DataverseRequest dataverseRequest = new DataverseRequest(authUser, httpServletRequest);
commandEngine.submit(new MoveDatasetCommand(
dataverseRequest, ds, target, false
));
dataverseRequest, ds, target, false, true));

logger.info("Moved " + dsPersistentId + " from " + srcAlias + " to " + dstAlias);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@
*/
package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetLinkingDataverse;
import edu.harvard.iq.dataverse.DatasetLock;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.Guestbook;
import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
Expand All @@ -21,11 +18,12 @@
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
import edu.harvard.iq.dataverse.engine.command.exception.UnforcedCommandException;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import java.util.logging.Logger;

/**
Expand All @@ -44,8 +42,12 @@ public class MoveDatasetCommand extends AbstractVoidCommand {
final Dataset moved;
final Dataverse destination;
final Boolean force;
private boolean allowSelfNotification = false;

public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse destination, Boolean force) {
this( aRequest, moved, destination, force, Boolean.FALSE);
}
public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse destination, Boolean force, Boolean allowSelfNotification) {
super(
aRequest,
dv("moved", moved),
Expand All @@ -54,6 +56,7 @@ public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse de
this.moved = moved;
this.destination = destination;
this.force= force;
this.allowSelfNotification = allowSelfNotification;
}

@Override
Expand Down Expand Up @@ -145,12 +148,55 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
}

// OK, move
Dataverse originalOwner = moved.getOwner();
moved.setOwner(destination);
ctxt.em().merge(moved);
sendNotification(moved, originalOwner, ctxt);

boolean doNormalSolrDocCleanUp = true;
ctxt.index().asyncIndexDataset(moved, doNormalSolrDocCleanUp);

}
/**
* Sends notifications to those able to publish the dataset upon the successful move of a dataset.
* <p>
* This method checks if dataset move notifications are enabled. If so, it
* notifies all users with {@code Permission.PublishDataset} on the original owning Dataverse.
* The user who initiated the action can be included or excluded from this
* notification based on the allowSelfNotification flag.
*
* @param dataset The moved {@code Dataset}.
* @param originalOwner The original owning {@code Dataverse}.
* @param ctxt The {@code CommandContext} providing access to application services.
*/
protected void sendNotification(Dataset dataset, Dataverse originalOwner, CommandContext ctxt) {
// 1. Exit early if the SendNotificationOnDatasetMove setting is disabled.
if (!ctxt.settings().isTrueForKey(SettingsServiceBean.Key.SendNotificationOnDatasetMove, false)) {
return;
}

// 2. Identify the user who initiated the action.
final User user = getUser();
final AuthenticatedUser requestor = user.isAuthenticated() ? (AuthenticatedUser) user : null;

// 3. Get all users with publish permission on the dataset's original owner (dataverse) and notify them.
Map<String, AuthenticatedUser> recipients = ctxt.permissions().getDistinctUsersWithPermissionOn(Permission.PublishDataset, originalOwner);
// make sure the requestor is in the recipient list in case they don't match the permission
if (requestor != null) {
recipients.put(requestor.getIdentifier(), requestor);
}

recipients.values()
.stream()
.filter(recipient -> allowSelfNotification || !recipient.equals(requestor))
.forEach(recipient -> ctxt.notifications().sendNotification(
recipient,
Timestamp.from(Instant.now()),
UserNotification.Type.DATASETMOVED,
dataset.getId(),
null,
requestor,
true
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,12 @@ Whether Harvesting (OAI) service is enabled
* ability/permission necessary to publish the dataset
*/
SendNotificationOnDatasetCreation,
/**
* A boolean setting that, if true will send an email and notification to users
* when a Dataset is moved. Messages go to those who have the
* ability/permission necessary to publish the dataset
*/
SendNotificationOnDatasetMove,
/**
* A JSON Object containing named comma separated sets(s) of allowed labels (up
* to 32 characters, spaces allowed) that can be set on draft datasets, via API
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/util/MailUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public static String getSubjectTextBasedOnNotification(UserNotification userNoti
return BundleUtil.getStringFromBundle("notification.email.rejected.file.access.subject", rootDvNameAsList);
case DATASETCREATED:
return BundleUtil.getStringFromBundle("notification.email.dataset.created.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
case DATASETMOVED:
return BundleUtil.getStringFromBundle("notification.email.dataset.moved.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
case CREATEDS:
return BundleUtil.getStringFromBundle("notification.email.create.dataset.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
case SUBMITTEDDS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ public void addFieldsByType(final NullSafeJsonBuilder notificationJson, final Au
case DATASETMENTIONED:
addDatasetMentionedFields(notificationJson, userNotification);
break;
case DATASETMOVED:
addDatasetMovedFields(notificationJson, userNotification, requestor);
break;
}
}

Expand Down Expand Up @@ -262,4 +265,9 @@ private void addDatasetMentionedFields(final NullSafeJsonBuilder notificationJso
addDatasetFields(notificationJson, userNotification);
notificationJson.add(KEY_ADDITIONAL_INFO, userNotification.getAdditionalInfo());
}

private void addDatasetMovedFields(final NullSafeJsonBuilder notificationJson, final UserNotification userNotification, final AuthenticatedUser requestor) {
addDatasetFields(notificationJson, userNotification);
addRequestorFields(notificationJson, requestor);
}
}
4 changes: 4 additions & 0 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ notification.createDataverse={0} was created in {1} . To learn more about what y
notification.dataverse.management.title=Dataverse Management - Dataverse User Guide
notification.createDataset={0} was created in {1}. To learn more about what you can do with a dataset, check out the {2}.
notification.datasetCreated={0} was created in {1} by {2}.
notification.datasetMoved={0} was moved to {1} by {2}.
notification.dataset.management.title=Dataset Management - Dataset User Guide
notification.wasSubmittedForReview={0} was submitted for review to be published in {1}. Don''t forget to publish it or send it back to the contributor, {2} ({3})\!
notification.wasReturnedByReviewer={0} was returned by the curator of {1}.
Expand Down Expand Up @@ -315,6 +316,7 @@ 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.typeDescription.DATASETMENTIONED=Dataset was referenced in remote system
notification.typeDescription.DATASETMOVED=Dataset was moved by user
notification.typeDescription.PIDRECONCILED=The Persistent identifier of dataset has been updated
notification.typeDescription.GLOBUSUPLOADCOMPLETED=Globus upload is completed
notification.typeDescription.GLOBUSUPLOADCOMPLETEDWITHERRORS=Globus upload completed with errors
Expand Down Expand Up @@ -815,6 +817,7 @@ dashboard.move.dataverse.menu.invalidMsg=No matches found
notification.email.create.dataverse.subject={0}: Your dataverse has been created
notification.email.create.dataset.subject={0}: Dataset "{1}" has been created
notification.email.dataset.created.subject={0}: Dataset "{1}" has been created
notification.email.dataset.moved.subject={0}: Dataset "{1}" has been moved
notification.email.request.file.access.subject={0}: {1} {2} ({3}) requested access to dataset "{4}"
notification.email.requested.file.access.subject={0}: You have requested access to a restricted file in dataset "{1}"
notification.email.grant.file.access.subject={0}: You have been granted access to a restricted file
Expand Down Expand Up @@ -865,6 +868,7 @@ notification.email.changeEmail=Hello, {0}.{1}\n\nPlease contact us if you did no
notification.email.passwordReset=Hi {0},\n\nSomeone, hopefully you, requested a password reset for {1}.\n\nPlease click the link below to reset your Dataverse account password:\n\n {2} \n\n The link above will only work for the next {3} minutes.\n\n Please contact us if you did not request this password reset or need further help.
notification.email.passwordReset.subject=Dataverse Password Reset Requested
notification.email.datasetWasCreated=Dataset "<a href = "{0}">{1}</a>" was just created by {2} in the {3} collection.
notification.email.datasetWasMoved=Dataset "<a href = "{0}">{1}</a>" was just moved by {2} to the {3} collection.
notification.email.requestedFileAccess=You have requested access to a file(s) in dataset "<a href = "{0}">{1}</a>". Your request has been sent to the managers of this dataset who will grant or reject your request. If you have any questions, you may reach the dataset managers using the "Contact" link on the upper right corner of the dataset page.
hours=hours
hour=hour
Expand Down
14 changes: 14 additions & 0 deletions src/main/webapp/dataverseuser.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@
</o:param>
</h:outputFormat>
</ui:fragment>
<ui:fragment rendered="#{item.type == 'DATASETMOVED'}">
<span class="icon-dataset text-icon-inline text-muted"></span>
<h:outputFormat value="#{bundle['notification.datasetMoved']}" escape="false">
<o:param>
<a href="/dataset.xhtml?persistentId=#{item.theObject.getGlobalId()}" title="#{item.theObject.getDisplayName()}">#{item.theObject.getDisplayName()}</a>
</o:param>
<o:param>
<a href="/dataverse/#{item.theObject.getOwner().getAlias()}" title="#{item.theObject.getOwner().getDisplayName()}">#{item.theObject.getOwner().getDisplayName()}</a>
</o:param>
<o:param>
#{item.requestor.name}
</o:param>
</h:outputFormat>
</ui:fragment>
<ui:fragment rendered="#{item.type == 'SUBMITTEDDS'}">
<span class="icon-dataset text-icon-inline text-muted"></span>
<h:outputFormat value="#{bundle['notification.wasSubmittedForReview']}" escape="false">
Expand Down
Loading