Skip to content

Commit f5049ac

Browse files
authored
Merge pull request #11805 from IQSS/11670-notification-of-moved-datasets
New Dataset Move Notification
2 parents 954697d + 3359c1b commit f5049ac

14 files changed

Lines changed: 232 additions & 29 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Notifications
2+
3+
New notification was added for Datasets moving between Dataverses.
4+
Requires SettingsServiceBean.Key.SendNotificationOnDatasetMove setting to be enabled.
5+
6+
See #11670

doc/sphinx-guides/source/admin/user-administration.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ This enables additional settings for each user in the notifications tab of their
9898
* ``CREATEDS`` Your dataset is created
9999
* ``CREATEDV`` Dataverse collection is created
100100
* ``DATASETCREATED`` Dataset was created by user
101+
* ``DATASETMOVED`` Dataset was moved by user
101102
* ``FILESYSTEMIMPORT`` Dataset has been successfully uploaded and verified
102103
* ``GRANTFILEACCESS`` Access to file is granted
103104
* ``INGESTCOMPLETEDWITHERRORS`` Ingest completed with errors

src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,12 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio
478478
String[] paramArrayDatasetCreated = {getDatasetLink(dataset), dataset.getDisplayName(), userNotification.getRequestor().getName(), dataset.getOwner().getDisplayName()};
479479
messageText += MessageFormat.format(pattern, paramArrayDatasetCreated);
480480
return messageText;
481+
case DATASETMOVED:
482+
dataset = (Dataset) targetObject;
483+
pattern = BundleUtil.getStringFromBundle("notification.email.datasetWasMoved");
484+
String[] paramArrayDatasetMoved = {getDatasetLink(dataset), dataset.getDisplayName(), userNotification.getRequestor().getName(), dataset.getOwner().getDisplayName()};
485+
messageText += MessageFormat.format(pattern, paramArrayDatasetMoved);
486+
return messageText;
481487
case CREATEDS:
482488
version = (DatasetVersion) targetObject;
483489
String datasetCreatedMessage = BundleUtil.getStringFromBundle("notification.email.createDataset", Arrays.asList(
@@ -785,6 +791,7 @@ public Object getObjectOfNotification (UserNotification userNotification){
785791
case GRANTFILEACCESS:
786792
case REJECTFILEACCESS:
787793
case DATASETCREATED:
794+
case DATASETMOVED:
788795
case DATASETMENTIONED:
789796
return datasetService.find(userNotification.getObjectId());
790797
case CREATEDS:

src/main/java/edu/harvard/iq/dataverse/UserNotification.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public enum Type {
4040
PUBLISHFAILED_PIDREG, WORKFLOW_SUCCESS, WORKFLOW_FAILURE, STATUSUPDATED, DATASETCREATED, DATASETMENTIONED,
4141
GLOBUSUPLOADCOMPLETED, GLOBUSUPLOADCOMPLETEDWITHERRORS,
4242
GLOBUSDOWNLOADCOMPLETED, GLOBUSDOWNLOADCOMPLETEDWITHERRORS, REQUESTEDFILEACCESS,
43-
GLOBUSUPLOADREMOTEFAILURE, GLOBUSUPLOADLOCALFAILURE, PIDRECONCILED;
43+
GLOBUSUPLOADREMOTEFAILURE, GLOBUSUPLOADLOCALFAILURE, PIDRECONCILED,
44+
DATASETMOVED;
4445

4546
public String getDescription() {
4647
return BundleUtil.getStringFromBundle("notification.typeDescription." + this.name());

src/main/java/edu/harvard/iq/dataverse/api/Datasets.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,8 +1430,7 @@ public Response moveDataset(@Context ContainerRequestContext crc, @PathParam("id
14301430
}
14311431
//Command requires Super user - it will be tested by the command
14321432
execCommand(new MoveDatasetCommand(
1433-
createDataverseRequest(u), ds, target, force
1434-
));
1433+
createDataverseRequest(u), ds, target, force, true));
14351434
return ok(BundleUtil.getStringFromBundle("datasets.api.moveDataset.success"));
14361435
} catch (WrappedResponse ex) {
14371436
if (ex.getCause() instanceof UnforcedCommandException) {

src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/DataverseUserPage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ public void displayNotification() {
509509
case GRANTFILEACCESS:
510510
case REJECTFILEACCESS:
511511
case DATASETCREATED:
512+
case DATASETMOVED:
512513
case DATASETMENTIONED:
513514
userNotification.setTheObject(datasetService.find(userNotification.getObjectId()));
514515
break;

src/main/java/edu/harvard/iq/dataverse/dashboard/DashboardMoveDatasetPage.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ public void move(){
153153
HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
154154
DataverseRequest dataverseRequest = new DataverseRequest(authUser, httpServletRequest);
155155
commandEngine.submit(new MoveDatasetCommand(
156-
dataverseRequest, ds, target, false
157-
));
156+
dataverseRequest, ds, target, false, true));
158157

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

src/main/java/edu/harvard/iq/dataverse/engine/command/impl/MoveDatasetCommand.java

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
*/
66
package edu.harvard.iq.dataverse.engine.command.impl;
77

8-
import edu.harvard.iq.dataverse.Dataset;
9-
import edu.harvard.iq.dataverse.DatasetLinkingDataverse;
10-
import edu.harvard.iq.dataverse.DatasetLock;
11-
import edu.harvard.iq.dataverse.Dataverse;
12-
import edu.harvard.iq.dataverse.Guestbook;
8+
import edu.harvard.iq.dataverse.*;
139
import edu.harvard.iq.dataverse.authorization.Permission;
1410
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
11+
import edu.harvard.iq.dataverse.authorization.users.User;
1512
import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand;
1613
import edu.harvard.iq.dataverse.engine.command.CommandContext;
1714
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
@@ -21,11 +18,12 @@
2118
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
2219
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
2320
import edu.harvard.iq.dataverse.engine.command.exception.UnforcedCommandException;
21+
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
2422
import edu.harvard.iq.dataverse.util.BundleUtil;
25-
import java.util.ArrayList;
26-
import java.util.Arrays;
27-
import java.util.Collections;
28-
import java.util.List;
23+
24+
import java.sql.Timestamp;
25+
import java.time.Instant;
26+
import java.util.*;
2927
import java.util.logging.Logger;
3028

3129
/**
@@ -34,8 +32,8 @@
3432
* @author skraffmi
3533
*/
3634
@RequiredPermissionsMap({
37-
@RequiredPermissions(dataverseName = "moved", value = {Permission.PublishDataset})
38-
, @RequiredPermissions(dataverseName = "destination", value = {Permission.AddDataset, Permission.PublishDataset})
35+
@RequiredPermissions(dataverseName = "moved", value = {Permission.PublishDataset}),
36+
@RequiredPermissions(dataverseName = "destination", value = {Permission.AddDataset, Permission.PublishDataset})
3937
})
4038
public class MoveDatasetCommand extends AbstractVoidCommand {
4139

@@ -44,8 +42,13 @@ public class MoveDatasetCommand extends AbstractVoidCommand {
4442
final Dataset moved;
4543
final Dataverse destination;
4644
final Boolean force;
45+
final Boolean allowSelfNotification;
46+
final Dataverse originalOwner;
4747

4848
public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse destination, Boolean force) {
49+
this( aRequest, moved, destination, force, null);
50+
}
51+
public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse destination, Boolean force, Boolean allowSelfNotification) {
4952
super(
5053
aRequest,
5154
dv("moved", moved),
@@ -54,6 +57,8 @@ public MoveDatasetCommand(DataverseRequest aRequest, Dataset moved, Dataverse de
5457
this.moved = moved;
5558
this.destination = destination;
5659
this.force= force;
60+
this.allowSelfNotification = allowSelfNotification;
61+
this.originalOwner = moved.getOwner();
5762
}
5863

5964
@Override
@@ -72,13 +77,13 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
7277
if (moved.getOwner().equals(destination)) {
7378
throw new IllegalCommandException(BundleUtil.getStringFromBundle("dashboard.move.dataset.command.error.targetDataverseSameAsOriginalDataverse"), this);
7479
}
75-
80+
7681
// if dataset is published make sure that its target is published
77-
82+
7883
if (moved.isReleased() && !destination.isReleased()){
7984
throw new IllegalCommandException(BundleUtil.getStringFromBundle("dashboard.move.dataset.command.error.targetDataverseUnpublishedDatasetPublished", Arrays.asList(destination.getDisplayName())), this);
8085
}
81-
86+
8287
//if the datasets guestbook is not contained in the new dataverse then remove it
8388
if (moved.getGuestbook() != null) {
8489
Guestbook gb = moved.getGuestbook();
@@ -97,15 +102,15 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
97102
}
98103
}
99104
}
100-
105+
101106
// generate list of all possible parent dataverses to check against
102107
List<Dataverse> ownersToCheck = new ArrayList<>();
103108
ownersToCheck.add(destination);
104109
if (destination.getOwners() != null) {
105110
ownersToCheck.addAll(destination.getOwners());
106111
}
107-
108-
// if the dataset is linked to the new dataverse or any of
112+
113+
// if the dataset is linked to the new dataverse or any of
109114
// its parent dataverses then remove the link
110115
List<DatasetLinkingDataverse> linkingDatasets = new ArrayList<>();
111116
if (moved.getDatasetLinkingDataverses() != null) {
@@ -124,7 +129,7 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
124129
}
125130
}
126131
}
127-
132+
128133
if (removeGuestbook || removeLinkDs) {
129134
StringBuilder errorString = new StringBuilder();
130135
if (removeGuestbook) {
@@ -135,10 +140,10 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
135140
}
136141
throw new UnforcedCommandException(errorString.toString(), this);
137142
}
138-
143+
139144
// 6575 if dataset is submitted for review and the default contributor
140145
// role includes dataset publish then remove the lock
141-
146+
142147
if (moved.isLockedFor(DatasetLock.Reason.InReview)
143148
&& destination.getDefaultContributorRole().permissions().contains(Permission.PublishDataset)) {
144149
ctxt.datasets().removeDatasetLocks(moved, DatasetLock.Reason.InReview);
@@ -153,4 +158,56 @@ public void executeImpl(CommandContext ctxt) throws CommandException {
153158

154159
}
155160

161+
@Override
162+
public boolean onSuccess(CommandContext ctxt, Object r) {
163+
sendNotification(moved, originalOwner, ctxt);
164+
return true;
165+
}
166+
167+
/**
168+
* Sends notifications to those able to publish the dataset upon the successful move of a dataset.
169+
* <p>
170+
* This method checks if dataset move notifications are enabled. If so, it
171+
* notifies all users with {@code Permission.PublishDataset} on the original owning Dataverse.
172+
* The user who initiated the action can be included or excluded from this
173+
* notification based on the allowSelfNotification flag.
174+
*
175+
* @param dataset The moved {@code Dataset}.
176+
* @param originalOwner The original owning {@code Dataverse}.
177+
* @param ctxt The {@code CommandContext} providing access to application services.
178+
*/
179+
protected void sendNotification(Dataset dataset, Dataverse originalOwner, CommandContext ctxt) {
180+
// 1. Exit early if the SendNotificationOnDatasetMove setting is disabled.
181+
if (!ctxt.settings().isTrueForKey(SettingsServiceBean.Key.SendNotificationOnDatasetMove, false)) {
182+
return;
183+
}
184+
185+
// 2. Identify the user who initiated the action.
186+
final User user = getUser();
187+
final AuthenticatedUser requestor = user.isAuthenticated() ? (AuthenticatedUser) user : null;
188+
189+
// 3. Get all users with publish permission on the dataset's original owner (dataverse) and notify them.
190+
Map<String, AuthenticatedUser> recipients = ctxt.permissions().getDistinctUsersWithPermissionOn(Permission.PublishDataset, originalOwner);
191+
// make sure the requestor is in the recipient list in case they don't match the permission but only if allowSelfNotification is true
192+
if (requestor != null) {
193+
if (Boolean.TRUE.equals(allowSelfNotification)) {
194+
recipients.put(requestor.getIdentifier(), requestor);
195+
} else {
196+
recipients.remove(requestor.getIdentifier());
197+
}
198+
}
199+
200+
recipients.values()
201+
.stream()
202+
.forEach(recipient -> ctxt.notifications().sendNotification(
203+
recipient,
204+
Timestamp.from(Instant.now()),
205+
UserNotification.Type.DATASETMOVED,
206+
dataset.getId(),
207+
null,
208+
requestor,
209+
true
210+
));
211+
}
156212
}
213+

src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,12 @@ Whether Harvesting (OAI) service is enabled
652652
* ability/permission necessary to publish the dataset
653653
*/
654654
SendNotificationOnDatasetCreation,
655+
/**
656+
* A boolean setting that, if true will send an email and notification to users
657+
* when a Dataset is moved. Messages go to those who have the
658+
* ability/permission necessary to publish the dataset
659+
*/
660+
SendNotificationOnDatasetMove,
655661
/**
656662
* A JSON Object containing named comma separated sets(s) of allowed labels (up
657663
* to 32 characters, spaces allowed) that can be set on draft datasets, via API

src/main/java/edu/harvard/iq/dataverse/util/MailUtil.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public static String getSubjectTextBasedOnNotification(UserNotification userNoti
4747
return BundleUtil.getStringFromBundle("notification.email.rejected.file.access.subject", rootDvNameAsList);
4848
case DATASETCREATED:
4949
return BundleUtil.getStringFromBundle("notification.email.dataset.created.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
50+
case DATASETMOVED:
51+
return BundleUtil.getStringFromBundle("notification.email.dataset.moved.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
5052
case CREATEDS:
5153
return BundleUtil.getStringFromBundle("notification.email.create.dataset.subject", Arrays.asList(rootDvNameAsList.get(0), datasetDisplayName));
5254
case SUBMITTEDDS:

0 commit comments

Comments
 (0)