Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Returning to author now requires a reason that is sent by email to the author #10137

Merged
merged 11 commits into from
Mar 6, 2024
Merged
4 changes: 4 additions & 0 deletions doc/release-notes/3702-return-to-author.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Return to author

Popup for returning to author now requires a reason that will be sent by email to the author.
Please note that you can still type a creative and meaningful comment such as "The author would like to modify his dataset", "Files are missing", "Nothing to report" or "A curation report with comments and suggestions/instructions will follow in another email" that suits your situation.
3 changes: 2 additions & 1 deletion doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2102,7 +2102,8 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/:persistentId/returnToAuthor?persistentId=doi:10.5072/FK2/J8SJZB" -H "Content-type: application/json" -d @reason-for-return.json

The review process can sometimes resemble a tennis match, with the authors submitting and resubmitting the dataset over and over until the curators are satisfied. Each time the curators send a "reason for return" via API, that reason is persisted into the database, stored at the dataset version level.
The review process can sometimes resemble a tennis match, with the authors submitting and resubmitting the dataset over and over until the curators are satisfied. Each time the curators send a "reason for return" via API, that reason is sent by email and is persisted into the database, stored at the dataset version level.
The reason is required, please note that you can still type a creative and meaningful comment such as "The author would like to modify his dataset", "Files are missing", "Nothing to report" or "A curation report with comments and suggestions/instructions will follow in another email" that suits your situation.

The :ref:`send-feedback` API call may be useful as a way to move the conversation to email. However, note that these emails go to contacts (versus authors) and there is no database record of the email contents. (:ref:`dataverse.mail.cc-support-on-contact-email` will send a copy of these emails to the support email address which would provide a record.)

Expand Down
13 changes: 11 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,16 @@ public void setNumberOfFilesToShow(Long numberOfFilesToShow) {
this.numberOfFilesToShow = numberOfFilesToShow;
}

private String returnReason = "";
luddaniel marked this conversation as resolved.
Show resolved Hide resolved

public String getReturnReason() {
return returnReason;
}

public void setReturnReason(String returnReason) {
this.returnReason = returnReason;
}

public void showAll(){
setNumberOfFilesToShow(new Long(fileMetadatasSearch.size()));
}
Expand Down Expand Up @@ -2652,8 +2662,7 @@ public void edit(EditMode editMode) {

public String sendBackToContributor() {
try {
//FIXME - Get Return Comment from sendBackToContributor popup
Command<Dataset> cmd = new ReturnDatasetToAuthorCommand(dvRequestService.getDataverseRequest(), dataset, "");
Command<Dataset> cmd = new ReturnDatasetToAuthorCommand(dvRequestService.getDataverseRequest(), dataset, returnReason);
dataset = commandEngine.submit(cmd);
JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("dataset.reject.success"));
} catch (CommandException ex) {
Expand Down
26 changes: 16 additions & 10 deletions src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -466,18 +466,24 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio
case RETURNEDDS:
version = (DatasetVersion) targetObject;
pattern = BundleUtil.getStringFromBundle("notification.email.wasReturnedByReviewer");
String optionalReturnReason = "";
/*
FIXME
Setting up to add single comment when design completed
optionalReturnReason = ".";
if (comment != null && !comment.isEmpty()) {
optionalReturnReason = ".\n\n" + BundleUtil.getStringFromBundle("wasReturnedReason") + "\n\n" + comment;
}
*/

String[] paramArrayReturnedDataset = {version.getDataset().getDisplayName(), getDatasetDraftLink(version.getDataset()),
version.getDataset().getOwner().getDisplayName(), getDataverseLink(version.getDataset().getOwner()), optionalReturnReason};
version.getDataset().getOwner().getDisplayName(), getDataverseLink(version.getDataset().getOwner())};
messageText += MessageFormat.format(pattern, paramArrayReturnedDataset);

if (comment != null && !comment.isEmpty()) {
messageText += "\n\n" + MessageFormat.format(BundleUtil.getStringFromBundle("notification.email.wasReturnedByReviewerReason"), comment);
}

Dataverse d = (Dataverse) version.getDataset().getOwner();
List<String> contactEmailList = new ArrayList<String>();
for (DataverseContact dc : d.getDataverseContacts()) {
contactEmailList.add(dc.getContactEmail());
}
if (!contactEmailList.isEmpty()) {
String contactEmails = String.join(", ", contactEmailList);
messageText += "\n\n" + MessageFormat.format(BundleUtil.getStringFromBundle("notification.email.wasReturnedByReviewer.collectionContacts"), contactEmails);
}
return messageText;

case WORKFLOW_SUCCESS:
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 @@ -2143,9 +2143,8 @@ public Response returnToAuthor(@Context ContainerRequestContext crc, @PathParam(
Dataset dataset = findDatasetOrDie(idSupplied);
String reasonForReturn = null;
reasonForReturn = json.getString("reasonForReturn");
// TODO: Once we add a box for the curator to type into, pass the reason for return to the ReturnDatasetToAuthorCommand and delete this check and call to setReturnReason on the API side.
if (reasonForReturn == null || reasonForReturn.isEmpty()) {
luddaniel marked this conversation as resolved.
Show resolved Hide resolved
return error(Response.Status.BAD_REQUEST, "You must enter a reason for returning a dataset to the author(s).");
return error(Response.Status.BAD_REQUEST, BundleUtil.getStringFromBundle("dataset.reject.datasetNotInReview"));
}
AuthenticatedUser authenticatedUser = getRequestAuthenticatedUserOrDie(crc);
Dataset updatedDataset = execCommand(new ReturnDatasetToAuthorCommand(createDataverseRequest(authenticatedUser), dataset, reasonForReturn ));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public Response getAllNotificationsForUser(@Context ContainerRequestContext crc)
notificationObjectBuilder.add("id", notification.getId());
notificationObjectBuilder.add("type", type.toString());
/* FIXME - Re-add reasons for return if/when they are added to the notifications page.

if (Type.RETURNEDDS.equals(type) || Type.SUBMITTEDDS.equals(type)) {
JsonArrayBuilder reasons = getReasonsForReturn(notification);
for (JsonValue reason : reasons.build()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public class ReturnDatasetToAuthorCommand extends AbstractDatasetCommand<Dataset

public ReturnDatasetToAuthorCommand(DataverseRequest aRequest, Dataset anAffectedDvObject, String comment) {
super(aRequest, anAffectedDvObject);

if (comment == null || comment.isEmpty()) {
throw new IllegalArgumentException(BundleUtil.getStringFromBundle("dataset.reject.commentNull"));
}

this.comment = comment;
}

Expand Down
13 changes: 8 additions & 5 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -783,10 +783,12 @@ notification.email.rejectFileAccess=Your request for access was rejected for the
notification.email.createDataverse=Your new dataverse named {0} (view at {1} ) was created in {2} (view at {3} ). To learn more about what you can do with your dataverse, check out the Dataverse Management - User Guide at {4}/{5}/user/dataverse-management.html .
# Bundle file editors, please note that "notification.email.createDataset" is used in a unit test
notification.email.createDataset=Your new dataset named {0} (view at {1} ) was created in {2} (view at {3} ). To learn more about what you can do with a dataset, check out the Dataset Management - User Guide at {4}/{5}/user/dataset-management.html .
notification.email.wasSubmittedForReview={0} (view at {1} ) was submitted for review to be published in {2} (view at {3} ). Don''t forget to publish it or send it back to the contributor, {4} ({5})\!
notification.email.wasReturnedByReviewer={0} (view at {1} ) was returned by the curator of {2} (view at {3} ).
notification.email.wasPublished={0} (view at {1} ) was published in {2} (view at {3} ).
notification.email.publishFailedPidReg={0} (view at {1} ) in {2} (view at {3} ) could not be published due to a failure to register, or update the Global Identifier for the dataset or one of the files in it. Contact support if this continues to happen.
notification.email.wasSubmittedForReview=Your dataset named {0} (view at {1} ) was submitted for review to be published in {2} (view at {3} ). Don''t forget to publish it or send it back to the contributor, {4} ({5})\!
notification.email.wasReturnedByReviewer=Your dataset named {0} (view at {1} ) was returned by the curator of {2} (view at {3} ).
notification.email.wasReturnedByReviewerReason=Here is the curator comment: {0}
notification.email.wasReturnedByReviewer.collectionContacts=You may contact the collection administrator for more information: {0}
notification.email.wasPublished=Your dataset named {0} (view at {1} ) was published in {2} (view at {3} ).
notification.email.publishFailedPidReg=Your dataset named {0} (view at {1} ) in {2} (view at {3} ) could not be published due to a failure to register, or update the Global Identifier for the dataset or one of the files in it. Contact support if this continues to happen.
notification.email.closing=\n\nYou may contact us for support at {0}.\n\nThank you,\n{1}
notification.email.closing.html=<br><br>You may contact us for support at {0}.<br><br>Thank you,<br>{1}
notification.email.assignRole=You are now {0} for the {1} "{2}" (view at {3} ).
Expand Down Expand Up @@ -1474,14 +1476,15 @@ dataset.submit.failure.inReview=You cannot submit this dataset for review becaus
dataset.status.failure.notallowed=Status update failed - label not allowed
dataset.status.failure.disabled=Status labeling disabled for this dataset
dataset.status.failure.isReleased=Latest version of dataset is already released. Status can only be set on draft versions
dataset.rejectMessage=Return this dataset to contributor for modification.
dataset.rejectMessage=Return this dataset to contributor for modification. The reason for return entered below will be sent by email to the author.
dataset.rejectMessage.label=Return to Author Reason
dataset.rejectWatermark=Please enter a reason for returning this dataset to its author(s).
dataset.reject.enterReason.error=Reason for return to author is required.
dataset.reject.success=This dataset has been sent back to the contributor.
dataset.reject.failure=Dataset Submission Return Failed - {0}
dataset.reject.datasetNull=Cannot return the dataset to the author(s) because it is null.
dataset.reject.datasetNotInReview=This dataset cannot be return to the author(s) because the latest version is not In Review. The author(s) needs to click Submit for Review first.
dataset.reject.commentNull=You must enter a reason for returning a dataset to the author(s).
dataset.publish.tip=Are you sure you want to publish this dataset? Once you do so it must remain published.
dataset.publish.terms.tip=This version of the dataset will be published with the following terms:
dataset.publish.terms.help.tip=To change the terms for this version, click the Cancel button and go to the Terms tab for this dataset.
Expand Down
31 changes: 17 additions & 14 deletions src/main/webapp/dataset.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -1838,27 +1838,30 @@
<p class="text-warning">
<span class="glyphicon glyphicon-warning-sign"/> #{bundle['dataset.rejectMessage']}
</p>
<ui:remove>
<!--FIXME update for new comments table -->
<p:inputTextarea id="returnReason" rows="3" value="#{DatasetPage.workingVersion.returnReason}" title="#{bundle['dataset.rejectMessage.label']}" maxlength="200" widgetVar="returnReason"
cols="70" counter="display" counterTemplate="{0} characters remaining." autoResize="false"
requiredMessage="#{bundle['dataset.reject.enterReason.error']}"/>
<!--FIXME validation error msg for returnReason was in confirmDialog using testReturnReason() in commandButton below but all that was removed
as it did not look like the usual validation method, added preferred requiredMessage attribute to inputTextarea above, need wiring #5717-->
<p>
<h:outputText id="display"/>
</p>
<p:watermark for="returnReason" value="#{bundle['dataset.rejectWatermark']}" id="returnReasonwatermark"/>
</ui:remove>

<p:inputTextarea id="returnReason" rows="4" value="#{DatasetPage.returnReason}" title="#{bundle['dataset.rejectMessage.label']}" maxlength="2000" widgetVar="returnReason"
cols="70" counter="display" counterTemplate="{0} characters remaining." autoResize="false"
required="#{param['DO_RETURN_TO_AUTHOR_VALIDATION']}"
requiredMessage="#{bundle['dataset.reject.enterReason.error']}"/>
<p>
<h:outputText id="display"/>
</p>
<p:watermark for="returnReason" value="#{bundle['dataset.rejectWatermark']}" id="returnReasonWatermark"/>
<h:message for="returnReason" styleClass="bg-danger text-danger"/>

<div class="button-block">
<p:commandButton styleClass="btn btn-default" value="#{bundle.continue}" onclick="PF('sendBackToContributor').hide()" action="#{DatasetPage.sendBackToContributor}"/>
<p:commandButton styleClass="btn btn-default" value="#{bundle.continue}"
update="@form"
oncomplete="PF('sendBackToContributor').show();if (args &amp;&amp; !args.validationFailed) returnToAuthorCommand();">
<f:param name="DO_RETURN_TO_AUTHOR_VALIDATION" value="true"/>
</p:commandButton>
<button class="btn btn-link" onclick="PF('sendBackToContributor').hide();
PF('blockDatasetForm').hide();" type="button">
#{bundle.cancel}
</button>
</div>
</p:dialog>
<p:remoteCommand name="returnToAuthorCommand" action="#{DatasetPage.sendBackToContributor}"/>
<p:remoteCommand name="returnToAuthorCommand" oncomplete="PF('sendBackToContributor').hide();" update=":messagePanel" actionListener="#{DatasetPage.sendBackToContributor}"/>
luddaniel marked this conversation as resolved.
Show resolved Hide resolved
<p:remoteCommand name="linkEditTerms" actionListener="#{DatasetPage.edit('LICENSE')}" update="@form,:datasetForm,:messagePanel">
<f:setPropertyActionListener target="#{DatasetPage.selectedTabIndex}" value="0" />
</p:remoteCommand>
Expand Down
3 changes: 0 additions & 3 deletions src/main/webapp/dataverseuser.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,6 @@
<o:param>
#{DataverseUserPage.getRequestorEmail(item)}
</o:param>
<o:param>
#{DataverseUserPage.getReasonForReturn(item.theObject)}
</o:param>
</h:outputFormat>
</ui:fragment>
<ui:fragment rendered="#{item.type == 'RETURNEDDS'}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void testReleasedDataset() {
String actual = null;
Dataset updatedDataset = null;
try {
updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, ""));
updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, "Update Your Files, Dummy"));
} catch (CommandException ex) {
actual = ex.getMessage();
}
Expand All @@ -171,36 +171,33 @@ public void testNotInReviewDataset() {
String actual = null;
Dataset updatedDataset = null;
try {
updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, ""));
updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, "Update Your Files, Dummy"));
} catch (CommandException ex) {
actual = ex.getMessage();
}
assertEquals(expected, actual);
}

/*
FIXME - Empty Comments won't be allowed in future
@Test
public void testEmptyComments(){

dataset.setIdentifier("DUMMY");
public void testEmptyOrNullComment(){
dataset.getLatestVersion().setVersionState(DatasetVersion.VersionState.DRAFT);
dataset.getLatestVersion().setInReview(true);
dataset.getLatestVersion().setReturnReason(null);
Dataset updatedDataset = null;
String expected = "You must enter a reason for returning a dataset to the author(s).";
String actual = null;
Dataset updatedDataset = null;
try {

updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset));
} catch (CommandException ex) {
testEngine.submit( new AddLockCommand(dataverseRequest, dataset,
new DatasetLock(DatasetLock.Reason.InReview, dataverseRequest.getAuthenticatedUser())));

assertThrowsExactly(IllegalArgumentException.class,
() -> new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, null), expected);
assertThrowsExactly(IllegalArgumentException.class,
() -> new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, ""), expected);
updatedDataset = testEngine.submit(new ReturnDatasetToAuthorCommand(dataverseRequest, dataset, ""));
} catch (IllegalArgumentException | CommandException ex) {
actual = ex.getMessage();
}
assertEquals(expected, actual);


assertEquals(expected, actual);
}
*/

@Test
public void testAllGood() {
Expand Down
Loading