Merge branch 'main' into CST-7754

This commit is contained in:
corrado lombardi
2023-02-13 16:57:49 +01:00
36 changed files with 1566 additions and 302 deletions

View File

@@ -303,6 +303,10 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini
commitCounter++;
if (commitCounter % 100 == 0) {
context.dispatchEvents();
// Commit actual changes to DB after dispatch events
System.out.print("Performing incremental commit to the database...");
context.commit();
System.out.println(" Incremental commit done!");
}
context.uncacheEntity(bitstream);

View File

@@ -18,6 +18,7 @@ import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
/**
* Service interface class for the WorkflowService framework.
@@ -100,6 +101,9 @@ public interface WorkflowService<T extends WorkflowItem> {
String rejection_message)
throws SQLException, AuthorizeException, IOException;
public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance)
throws SQLException, AuthorizeException, IOException, WorkflowException;
public String getMyDSpaceLink();
public void deleteCollection(Context context, Collection collection)

View File

@@ -41,6 +41,9 @@ public class Role implements BeanNameAware {
@Autowired
private WorkflowItemRoleService workflowItemRoleService;
// Whether or not to delete temporary group made attached to the WorkflowItemRole for this role in AutoAssignAction
private boolean deleteTemporaryGroup = false;
private String id;
private String name;
private String description;
@@ -153,4 +156,17 @@ public class Role implements BeanNameAware {
public void setInternal(boolean internal) {
isInternal = internal;
}
public boolean isDeleteTemporaryGroup() {
return deleteTemporaryGroup;
}
/**
* Setter for config that indicated whether or not to delete temporary group made attached to the
* WorkflowItemRole for this role in AutoAssignAction
* @param deleteTemporaryGroup
*/
public void setDeleteTemporaryGroup(boolean deleteTemporaryGroup) {
this.deleteTemporaryGroup = deleteTemporaryGroup;
}
}

View File

@@ -1076,6 +1076,53 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
return wsi;
}
@Override
public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance)
throws SQLException, AuthorizeException, IOException, WorkflowException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException("You must be an admin to restart a workflow");
}
context.turnOffAuthorisationSystem();
// rejection provenance
Item myitem = wi.getItem();
// Here's what happened
String provDescription =
provenance + " Declined by " + getEPersonName(decliner) + " on " + DCDate.getCurrent().toString() +
" (GMT) ";
// Add to item as a DC field
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
//Clear any workflow schema related metadata
itemService
.clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
itemService.update(context, myitem);
// remove policy for controller
removeUserItemPolicies(context, myitem, decliner);
revokeReviewerPolicies(context, myitem);
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(context, wi);
// Because of issue of xmlWorkflowItemService not realising wfi wrapper has been deleted
context.commit();
wsi = context.reloadEntity(wsi);
log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() +
"eperson_id=" + decliner.getID()));
// Restart workflow
this.startWithoutNotify(context, wsi);
context.restoreAuthSystemState();
}
/**
* Return the workflow item to the workspace of the submitter. The workflow
* item is removed, and a workspace item created.

View File

@@ -14,10 +14,15 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.RoleMembers;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,6 +42,8 @@ public abstract class Action {
private WorkflowActionConfig parent;
private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields";
private List<String> advancedOptions = new ArrayList<>();
private List<ActionAdvancedInfo> advancedInfo = new ArrayList<>();
/**
* Called when a workflow item becomes eligible for this Action.
@@ -192,4 +199,58 @@ public abstract class Action {
//save updated list
setErrorFields(request, errorFields);
}
/**
* Returns a list of advanced options that the user can select at this action
* @return A list of advanced options of this action, resulting in the next step of the workflow
*/
protected List<String> getAdvancedOptions() {
return advancedOptions;
}
/**
* Returns true if this Action has advanced options, false if it doesn't
* @return true if there are advanced options, false otherwise
*/
protected boolean isAdvanced() {
return !getAdvancedOptions().isEmpty();
}
/**
* Returns a list of advanced info required by the advanced options
* @return A list of advanced info required by the advanced options
*/
protected List<ActionAdvancedInfo> getAdvancedInfo() {
return advancedInfo;
}
/**
* Adds info in the metadata field dc.description.provenance about item being approved containing in which step
* it was approved, which user approved it and the time
*
* @param c DSpace contect
* @param wfi Workflow item we're adding workflow accept provenance on
*/
public void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName =
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by " + usersName + " on "
+ now + " (GMT) ";
// Add to item as a DC field
c.turnOffAuthorisationSystem();
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
c.restoreAuthSystemState();
}
}

View File

@@ -0,0 +1,42 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xmlworkflow.state.actions;
/**
* Interface for the shared properties of an 'advancedInfo' section of an advanced workflow {@link Action}
* Implementations of this class will define the specific fields per action that will need to be defined/configured
* to pass along this info to REST endpoint
*/
public abstract class ActionAdvancedInfo {
protected String type;
protected String id;
protected final static String TYPE_PREFIX = "action_info_";
public String getType() {
return type;
}
public void setType(String type) {
this.type = TYPE_PREFIX + type;
}
public String getId() {
return id;
}
/**
* Setter for the Action id to be set.
* This is an MD5 hash of the type and the stringified properties of the advanced info
*
* @param type The type of this Action to be included in the MD5 hash
*/
protected abstract void generateId(String type);
}

View File

@@ -69,4 +69,28 @@ public class WorkflowActionConfig {
return this.processingAction.getOptions();
}
/**
* Returns a list of advanced options this user has on this action, resulting in the next step of the workflow
* @return A list of advanced options of this action, resulting in the next step of the workflow
*/
public List<String> getAdvancedOptions() {
return this.processingAction.getAdvancedOptions();
}
/**
* Returns a boolean depending on whether this action has advanced options
* @return The boolean indicating whether this action has advanced options
*/
public boolean isAdvanced() {
return this.processingAction.isAdvanced();
}
/**
* Returns a Map of info for the advanced options this user has on this action
* @return a Map of info for the advanced options this user has on this action
*/
public List<ActionAdvancedInfo> getAdvancedInfo() {
return this.processingAction.getAdvancedInfo();
}
}

View File

@@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
@@ -34,8 +32,6 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
*/
public class AcceptEditRejectAction extends ProcessingAction {
private static final String SUBMIT_APPROVE = "submit_approve";
private static final String SUBMIT_REJECT = "submit_reject";
private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted";
//TODO: rename to AcceptAndEditMetadataAction
@@ -53,7 +49,7 @@ public class AcceptEditRejectAction extends ProcessingAction {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, request);
return super.processRejectPage(c, wfi, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
default:
@@ -69,33 +65,18 @@ public class AcceptEditRejectAction extends ProcessingAction {
options.add(SUBMIT_APPROVE);
options.add(SUBMIT_REJECT);
options.add(ProcessingAction.SUBMIT_EDIT_METADATA);
options.add(RETURN_TO_POOL);
return options;
}
public ActionResult processAccept(Context c, XmlWorkflowItem wfi)
throws SQLException, AuthorizeException {
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
// We have pressed reject, so remove the task the user has & put it back
// to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().sendWorkflowItemBackSubmission(c, wfi,
c.getCurrentUser(), this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {
@@ -111,21 +92,4 @@ public class AcceptEditRejectAction extends ProcessingAction {
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
}

View File

@@ -14,10 +14,7 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -52,7 +49,7 @@ public class FinalEditAction extends ProcessingAction {
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
default:
//We pressed the leave button so return to our submissions page
@@ -67,25 +64,8 @@ public class FinalEditAction extends ProcessingAction {
List<String> options = new ArrayList<>();
options.add(SUBMIT_APPROVE);
options.add(ProcessingAction.SUBMIT_EDIT_METADATA);
options.add(RETURN_TO_POOL);
return options;
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
}

View File

@@ -7,12 +7,16 @@
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.service.XmlWorkflowService;
import org.dspace.xmlworkflow.state.actions.Action;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService;
@@ -32,9 +36,15 @@ public abstract class ProcessingAction extends Action {
protected ClaimedTaskService claimedTaskService;
@Autowired(required = true)
protected ItemService itemService;
@Autowired
protected XmlWorkflowService xmlWorkflowService;
public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata";
public static final String SUBMIT_CANCEL = "submit_cancel";
protected static final String SUBMIT_APPROVE = "submit_approve";
protected static final String SUBMIT_REJECT = "submit_reject";
protected static final String RETURN_TO_POOL = "return_to_pool";
protected static final String REJECT_REASON = "reason";
@Override
public boolean isAuthorized(Context context, HttpServletRequest request, XmlWorkflowItem wfi) throws SQLException {
@@ -48,4 +58,31 @@ public abstract class ProcessingAction extends Action {
task.getStepID().equals(getParent().getStep().getId()) &&
task.getActionID().equals(getParent().getId());
}
/**
* Process result when option {@link this#SUBMIT_REJECT} is selected.
* - Sets the reason and workflow step responsible on item in dc.description.provenance
* - Send workflow back to the submission
* If reason is not given => error
*/
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter(REJECT_REASON);
if (reason == null || 0 == reason.trim().length()) {
addErrorField(request, REJECT_REASON);
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
// We have pressed reject, so remove the task the user has & put it back
// to a workspace item
xmlWorkflowService.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(),
reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
@Override
protected boolean isAdvanced() {
return !getAdvancedOptions().isEmpty();
}
}

View File

@@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
@@ -36,11 +34,8 @@ public class ReviewAction extends ProcessingAction {
public static final int MAIN_PAGE = 0;
public static final int REJECT_PAGE = 1;
private static final String SUBMIT_APPROVE = "submit_approve";
private static final String SUBMIT_REJECT = "submit_reject";
private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted";
@Override
public void activate(Context c, XmlWorkflowItem wfItem) {
@@ -54,7 +49,7 @@ public class ReviewAction extends ProcessingAction {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, step, request);
return super.processRejectPage(c, wfi, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
default:
@@ -69,50 +64,15 @@ public class ReviewAction extends ProcessingAction {
List<String> options = new ArrayList<>();
options.add(SUBMIT_APPROVE);
options.add(SUBMIT_REJECT);
options.add(RETURN_TO_POOL);
return options;
}
public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
request.setAttribute("page", REJECT_PAGE);
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//We have pressed reject, so remove the task the user has & put it back to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {

View File

@@ -7,6 +7,9 @@
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.REVIEW_FIELD;
import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.SCORE_FIELD;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -19,7 +22,6 @@ import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,6 +39,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
*/
public class ScoreEvaluationAction extends ProcessingAction {
// Minimum aggregate of scores
private int minimumAcceptanceScore;
@Override
@@ -47,43 +50,64 @@ public class ScoreEvaluationAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
boolean hasPassed = false;
//Retrieve all our scores from the metadata & add em up
// Retrieve all our scores from the metadata & add em up
int scoreMean = getMeanScore(wfi);
//We have passed if we have at least gained our minimum score
boolean hasPassed = getMinimumAcceptanceScore() <= scoreMean;
//Whether or not we have passed, clear our score information
itemService.clearMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier,
Item.ANY);
if (hasPassed) {
this.addRatingInfoToProv(c, wfi, scoreMean);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We haven't passed, reject our item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(),
"The item was reject due to a bad review score.");
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}
private int getMeanScore(XmlWorkflowItem wfi) {
List<MetadataValue> scores = itemService
.getMetadata(wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY);
.getMetadata(wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, Item.ANY);
int scoreMean = 0;
if (0 < scores.size()) {
int totalScoreCount = 0;
for (MetadataValue score : scores) {
totalScoreCount += Integer.parseInt(score.getValue());
}
int scoreMean = totalScoreCount / scores.size();
//We have passed if we have at least gained our minimum score
hasPassed = getMinimumAcceptanceScore() <= scoreMean;
//Wether or not we have passed, clear our score information
itemService
.clearMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY);
scoreMean = totalScoreCount / scores.size();
}
return scoreMean;
}
String provDescription = getProvenanceStartId() + " Approved for entry into archive with a score of: " +
scoreMean;
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
itemService.update(c, wfi.getItem());
private void addRatingInfoToProv(Context c, XmlWorkflowItem wfi, int scoreMean)
throws SQLException, AuthorizeException {
StringBuilder provDescription = new StringBuilder();
provDescription.append(String.format("%s Approved for entry into archive with a score of: %s",
getProvenanceStartId(), scoreMean));
List<MetadataValue> reviews = itemService
.getMetadata(wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element, REVIEW_FIELD.qualifier, Item.ANY);
if (!reviews.isEmpty()) {
provDescription.append(" | Reviews: ");
}
if (hasPassed) {
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We haven't passed, reject our item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(),
"The item was reject due to a bad review score.");
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
for (MetadataValue review : reviews) {
provDescription.append(String.format("; %s", review.getValue()));
}
c.turnOffAuthorisationSystem();
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription.toString());
itemService.update(c, wfi.getItem());
c.restoreAuthSystemState();
}
@Override
public List<String> getOptions() {
return new ArrayList<>();
List<String> options = new ArrayList<>();
options.add(RETURN_TO_POOL);
return options;
}
public int getMinimumAcceptanceScore() {

View File

@@ -9,14 +9,20 @@ package org.dspace.xmlworkflow.state.actions.processingaction;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.MetadataFieldName;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -24,40 +30,121 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
* This action will allow multiple users to rate a certain item
* if the mean of this score is higher then the minimum score the
* item will be sent to the next action/step else it will be rejected
*
* @author Bram De Schouwer (bram.deschouwer at dot com)
* @author Kevin Van de Velde (kevin at atmire dot com)
* @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com)
*/
public class ScoreReviewAction extends ProcessingAction {
private static final Logger log = LogManager.getLogger(ScoreReviewAction.class);
private static final String SUBMIT_SCORE = "submit_score";
// Option(s)
public static final String SUBMIT_SCORE = "submit_score";
// Response param(s)
private static final String SCORE = "score";
private static final String REVIEW = "review";
// Metadata fields to save params in
public static final MetadataFieldName SCORE_FIELD =
new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, SCORE, null);
public static final MetadataFieldName REVIEW_FIELD =
new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, REVIEW, null);
// Whether or not it is required that a text review is added to the rating
private boolean descriptionRequired;
// Maximum value rating is allowed to be
private int maxValue;
@Override
public void activate(Context c, XmlWorkflowItem wf) {
// empty
}
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
if (request.getParameter(SUBMIT_SCORE) != null) {
int score = Util.getIntParameter(request, "score");
//Add our score to the metadata
itemService.addMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, null,
String.valueOf(score));
itemService.update(c, wfi.getItem());
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We have pressed the leave button so return to our submission page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
throws SQLException, AuthorizeException {
if (super.isOptionInParam(request) &&
StringUtils.equalsIgnoreCase(Util.getSubmitButton(request, SUBMIT_CANCEL), SUBMIT_SCORE)) {
return processSetRating(c, wfi, request);
}
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
private ActionResult processSetRating(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException {
int score = Util.getIntParameter(request, SCORE);
String review = request.getParameter(REVIEW);
if (!this.checkRequestValid(score, review)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//Add our rating and review to the metadata
itemService.addMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, null,
String.valueOf(score));
if (StringUtils.isNotBlank(review)) {
itemService.addMetadata(c, wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element,
REVIEW_FIELD.qualifier, null, String.format("%s - %s", score, review));
}
itemService.update(c, wfi.getItem());
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
/**
* Request is not valid if:
* - Given score is higher than configured maxValue
* - There is no review given and description is configured to be required
* Config in workflow-actions.xml
*
* @param score Given score rating from request
* @param review Given review/description from request
* @return True if valid request params with config, otherwise false
*/
private boolean checkRequestValid(int score, String review) {
if (score > this.maxValue) {
log.error("{} only allows max rating {} (config workflow-actions.xml), given rating of " +
"{} not allowed.", this.getClass().toString(), this.maxValue, score);
return false;
}
if (StringUtils.isBlank(review) && this.descriptionRequired) {
log.error("{} has config descriptionRequired=true (workflow-actions.xml), so rating " +
"requests without 'review' query param containing description are not allowed",
this.getClass().toString());
return false;
}
return true;
}
@Override
public List<String> getOptions() {
return List.of(SUBMIT_SCORE, RETURN_TO_POOL);
}
@Override
protected List<String> getAdvancedOptions() {
return Arrays.asList(SUBMIT_SCORE);
}
@Override
protected List<ActionAdvancedInfo> getAdvancedInfo() {
ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo();
scoreReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired);
scoreReviewActionAdvancedInfo.setMaxValue(maxValue);
scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE);
scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE);
return Collections.singletonList(scoreReviewActionAdvancedInfo);
}
/**
* Setter that sets the descriptionRequired property from workflow-actions.xml
* @param descriptionRequired boolean whether a description is required
*/
public void setDescriptionRequired(boolean descriptionRequired) {
this.descriptionRequired = descriptionRequired;
}
/**
* Setter that sets the maxValue property from workflow-actions.xml
* @param maxValue integer of the maximum allowed value
*/
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
}

View File

@@ -0,0 +1,45 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.springframework.util.DigestUtils;
/**
* Class that holds the advanced information needed for the
* {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction}
* See config {@code workflow-actions.cfg}
*/
public class ScoreReviewActionAdvancedInfo extends ActionAdvancedInfo {
private boolean descriptionRequired;
private int maxValue;
public boolean isDescriptionRequired() {
return descriptionRequired;
}
public void setDescriptionRequired(boolean descriptionRequired) {
this.descriptionRequired = descriptionRequired;
}
public int getMaxValue() {
return maxValue;
}
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
@Override
public void generateId(String type) {
String idString = type
+ ";descriptionRequired," + descriptionRequired
+ ";maxValue," + maxValue;
super.id = DigestUtils.md5DigestAsHex(idString.getBytes());
}
}

View File

@@ -9,17 +9,27 @@ package org.dspace.xmlworkflow.state.actions.processingaction;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.xmlworkflow.Role;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,13 +47,13 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class SelectReviewerAction extends ProcessingAction {
public static final int SEARCH_RESULTS_PAGE = 1;
public static final int RESULTS_PER_PAGE = 5;
private static final Logger log = LogManager.getLogger(SelectReviewerAction.class);
private static final String SUBMIT_CANCEL = "submit_cancel";
private static final String SUBMIT_SEARCH = "submit_search";
private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer_";
private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer";
private static final String PARAM_REVIEWER = "eperson";
private static final String CONFIG_REVIEWER_GROUP = "action.selectrevieweraction.group";
private Role role;
@@ -53,6 +63,15 @@ public class SelectReviewerAction extends ProcessingAction {
@Autowired(required = true)
private WorkflowItemRoleService workflowItemRoleService;
@Autowired
private ConfigurationService configurationService;
@Autowired
private GroupService groupService;
private static Group selectFromReviewsGroup;
private static boolean selectFromReviewsGroupInitialised = false;
@Override
public void activate(Context c, XmlWorkflowItem wf) {
@@ -60,56 +79,128 @@ public class SelectReviewerAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
throws SQLException, AuthorizeException {
String submitButton = Util.getSubmitButton(request, SUBMIT_CANCEL);
//Check if our user has pressed cancel
if (submitButton.equals(SUBMIT_CANCEL)) {
//Send us back to the submissions page
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
} else if (submitButton.equals(SUBMIT_SEARCH)) {
//Perform the search
String query = request.getParameter("query");
int page = Util.getIntParameter(request, "result-page");
if (page == -1) {
page = 0;
}
int resultCount = ePersonService.searchResultCount(c, query);
List<EPerson> epeople = ePersonService.search(c, query, page * RESULTS_PER_PAGE, RESULTS_PER_PAGE);
request.setAttribute("eperson-result-count", resultCount);
request.setAttribute("eperson-results", epeople);
request.setAttribute("result-page", page);
request.setAttribute("page", SEARCH_RESULTS_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE, SEARCH_RESULTS_PAGE);
} else if (submitButton.startsWith(SUBMIT_SELECT_REVIEWER)) {
//Retrieve the identifier of the eperson which will do the reviewing
UUID reviewerId = UUID.fromString(submitButton.substring(submitButton.lastIndexOf("_") + 1));
EPerson reviewer = ePersonService.find(c, reviewerId);
//Assign the reviewer. The workflowitemrole will be translated into a task in the autoassign
WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c);
workflowItemRole.setEPerson(reviewer);
workflowItemRole.setRoleId(getRole().getId());
workflowItemRole.setWorkflowItem(wfi);
workflowItemRoleService.update(c, workflowItemRole);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
return processSelectReviewers(c, wfi, request);
}
//There are only 2 active buttons on this page, so if anything else happens just return an error
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
/**
* Method to handle the {@link this#SUBMIT_SELECT_REVIEWER} action:
* - will retrieve the reviewer(s) uuid from request (param {@link this#PARAM_REVIEWER})
* - assign them to a {@link WorkflowItemRole}
* - In {@link org.dspace.xmlworkflow.state.actions.userassignment.AutoAssignAction} these reviewer(s) will get
* claimed task for this {@link XmlWorkflowItem}
* Will result in error if:
* - No reviewer(s) uuid in request (param {@link this#PARAM_REVIEWER})
* - If none of the reviewer(s) uuid passed along result in valid EPerson
* - If the reviewer(s) passed along are not in {@link this#selectFromReviewsGroup} when it is set
*
* @param c current DSpace session
* @param wfi the item on which the action is to be performed
* @param request the current client request
* @return the result of performing the action
*/
private ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException {
//Retrieve the identifier of the eperson which will do the reviewing
String[] reviewerIds = request.getParameterValues(PARAM_REVIEWER);
if (ArrayUtils.isEmpty(reviewerIds)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
List<EPerson> reviewers = new ArrayList<>();
for (String reviewerId : reviewerIds) {
EPerson reviewer = ePersonService.find(c, UUID.fromString(reviewerId));
if (reviewer == null) {
log.warn("No EPerson found with uuid {}", reviewerId);
} else {
reviewers.add(reviewer);
}
}
if (!this.checkReviewersValid(c, reviewers)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
createWorkflowItemRole(c, wfi, reviewers);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
private boolean checkReviewersValid(Context c, List<EPerson> reviewers) throws SQLException {
if (reviewers.size() == 0) {
return false;
}
Group group = this.getGroup(c);
if (group != null) {
for (EPerson reviewer: reviewers) {
if (!groupService.isMember(c, reviewer, group)) {
log.error("Reviewers selected must be member of group {}", group.getID());
return false;
}
}
}
return true;
}
private WorkflowItemRole createWorkflowItemRole(Context c, XmlWorkflowItem wfi, List<EPerson> reviewers)
throws SQLException, AuthorizeException {
WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c);
workflowItemRole.setRoleId(getRole().getId());
workflowItemRole.setWorkflowItem(wfi);
if (reviewers.size() == 1) {
// 1 reviewer in workflowitemrole => will be translated into a claimed task in the autoassign
workflowItemRole.setEPerson(reviewers.get(0));
} else {
// multiple reviewers, create a temporary group and assign this group, the workflowitemrole will be
// translated into a claimed task for reviewers in the autoassign, where group will be deleted
c.turnOffAuthorisationSystem();
Group selectedReviewsGroup = groupService.create(c);
groupService.setName(selectedReviewsGroup, "selectedReviewsGroup_" + wfi.getID());
for (EPerson reviewer : reviewers) {
groupService.addMember(c, selectedReviewsGroup, reviewer);
}
workflowItemRole.setGroup(selectedReviewsGroup);
c.restoreAuthSystemState();
}
workflowItemRoleService.update(c, workflowItemRole);
return workflowItemRole;
}
@Override
public List<String> getOptions() {
List<String> options = new ArrayList<>();
options.add(SUBMIT_SEARCH);
options.add(SUBMIT_SELECT_REVIEWER);
options.add(RETURN_TO_POOL);
return options;
}
@Override
protected List<String> getAdvancedOptions() {
return Arrays.asList(SUBMIT_SELECT_REVIEWER);
}
@Override
protected List<ActionAdvancedInfo> getAdvancedInfo() {
List<ActionAdvancedInfo> advancedInfo = new ArrayList<>();
SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo();
if (getGroup(null) != null) {
selectReviewerActionAdvancedInfo.setGroup(getGroup(null).getID().toString());
}
selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER);
selectReviewerActionAdvancedInfo.generateId(SUBMIT_SELECT_REVIEWER);
advancedInfo.add(selectReviewerActionAdvancedInfo);
return advancedInfo;
}
public Role getRole() {
return role;
}
@@ -118,4 +209,49 @@ public class SelectReviewerAction extends ProcessingAction {
public void setRole(Role role) {
this.role = role;
}
/**
* Get the Reviewer group from the "action.selectrevieweraction.group" property in actions.cfg by its UUID or name
* Returns null if no (valid) group configured
*
* @return configured reviewers Group from property or null if none
*/
private Group getGroup(@Nullable Context context) {
if (selectFromReviewsGroupInitialised) {
return this.selectFromReviewsGroup;
}
if (context == null) {
context = new Context();
}
String groupIdOrName = configurationService.getProperty(CONFIG_REVIEWER_GROUP);
if (StringUtils.isNotBlank(groupIdOrName)) {
Group group = null;
try {
// try to get group by name
group = groupService.findByName(context, groupIdOrName);
if (group == null) {
// try to get group by uuid if not a name
group = groupService.find(context, UUID.fromString(groupIdOrName));
}
} catch (Exception e) {
// There is an issue with the reviewer group that is set; if it is not set then can be chosen
// from all epeople
log.error("Issue with determining matching group for config {}={} for reviewer group of " +
"select reviewers workflow", CONFIG_REVIEWER_GROUP, groupIdOrName);
}
this.selectFromReviewsGroup = group;
}
selectFromReviewsGroupInitialised = true;
return this.selectFromReviewsGroup;
}
/**
* To be used by IT, e.g. {@code XmlWorkflowServiceIT}, when defining new 'Reviewers' group
*/
static public void resetGroup() {
selectFromReviewsGroup = null;
selectFromReviewsGroupInitialised = false;
}
}

View File

@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.springframework.util.DigestUtils;
/**
* Class that holds the advanced information needed for the
* {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction}
* See config {@code workflow-actions.cfg}
*/
public class SelectReviewerActionAdvancedInfo extends ActionAdvancedInfo {
private String group;
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
@Override
public void generateId(String type) {
String idString = type
+ ";group," + group;
super.id = DigestUtils.md5DigestAsHex(idString.getBytes());
}
}

View File

@@ -13,11 +13,15 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
@@ -34,39 +38,59 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
* @author Mark Diggory (markd at atmire dot com)
*/
public class SingleUserReviewAction extends ProcessingAction {
public static final int MAIN_PAGE = 0;
public static final int REJECT_PAGE = 1;
public static final int SUBMITTER_IS_DELETED_PAGE = 2;
private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class);
public static final int OUTCOME_REJECT = 1;
protected static final String SUBMIT_APPROVE = "submit_approve";
protected static final String SUBMIT_REJECT = "submit_reject";
protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task";
@Override
public void activate(Context c, XmlWorkflowItem wfItem) {
// empty
}
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
int page = Util.getIntParameter(request, "page");
switch (page) {
case MAIN_PAGE:
return processMainPage(c, wfi, step, request);
case REJECT_PAGE:
return processRejectPage(c, wfi, step, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
throws SQLException, AuthorizeException, IOException, WorkflowException {
if (!super.isOptionInParam(request)) {
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processReject(c, wfi, request);
case SUBMIT_DECLINE_TASK:
return processDecline(c, wfi);
default:
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
}
/**
* Process {@link super#SUBMIT_REJECT} on this action, will either:
* - If submitter of item no longer exists => Permanently delete corresponding item (no wfi/wsi remaining)
* - Otherwise: reject item back to submission => becomes wsi of submitter again
*/
private ActionResult processReject(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, IOException, AuthorizeException {
if (wfi.getSubmitter() == null) {
// If the original submitter is no longer there, delete the task
return processDelete(c, wfi);
} else {
return super.processRejectPage(c, wfi, request);
}
}
/**
* Accept the workflow item => last step in workflow so will be archived
* Info on step & reviewer will be added on metadata dc.description.provenance of resulting item
*/
public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
@Override
public List<String> getOptions() {
List<String> options = new ArrayList<>();
@@ -76,87 +100,29 @@ public class SingleUserReviewAction extends ProcessingAction {
return options;
}
public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
if (request.getParameter(SUBMIT_APPROVE) != null) {
//Delete the tasks
addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else if (request.getParameter(SUBMIT_REJECT) != null) {
// Make sure we indicate which page we want to process
if (wfi.getSubmitter() == null) {
request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE);
} else {
request.setAttribute("page", REJECT_PAGE);
}
// We have pressed reject item, so take the user to a page where they can reject
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
} else if (request.getParameter(SUBMIT_DECLINE_TASK) != null) {
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, OUTCOME_REJECT);
} else {
//We pressed the leave button so return to our submissions page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
/**
* Since original submitter no longer exists, workflow item is permanently deleted
*/
private ActionResult processDelete(Context c, XmlWorkflowItem wfi)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_reject") != null) {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
request.setAttribute("page", REJECT_PAGE);
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//We have pressed reject, so remove the task the user has & put it back to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else {
//Cancel, go back to the main task page
request.setAttribute("page", MAIN_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
EPerson user = c.getCurrentUser();
c.turnOffAuthorisationSystem();
WorkspaceItem workspaceItem = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.abort(c, wfi, user);
ContentServiceFactory.getInstance().getWorkspaceItemService().deleteAll(c, workspaceItem);
c.restoreAuthSystemState();
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser());
// Delete and send user back to myDspace page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else if (request.getParameter("submit_keep_it") != null) {
// Do nothing, just send it back to myDspace page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else {
//Cancel, go back to the main task page
request.setAttribute("page", MAIN_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
/**
* Selected reviewer declines to review task, then the workflow is aborted and restarted
*/
private ActionResult processDecline(Context c, XmlWorkflowItem wfi)
throws SQLException, IOException, AuthorizeException, WorkflowException {
c.turnOffAuthorisationSystem();
xmlWorkflowService.restartWorkflow(c, wfi, c.getCurrentUser(), this.getProvenanceStartId());
c.restoreAuthSystemState();
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}

View File

@@ -80,6 +80,10 @@ public class AutoAssignAction extends UserSelectionAction {
}
//Delete our workflow item role since the users have been assigned
workflowItemRoleService.delete(c, workflowItemRole);
if (role.isDeleteTemporaryGroup() && workflowItemRole.getGroup() != null) {
// Delete temporary groups created after members have workflow task assigned
groupService.delete(c, workflowItemRole.getGroup());
}
}
} else {
log.warn(LogHelper.getHeader(c, "Error while executing auto assign action",
@@ -127,7 +131,7 @@ public class AutoAssignAction extends UserSelectionAction {
protected void createTaskForEPerson(Context c, XmlWorkflowItem wfi, Step step, WorkflowActionConfig actionConfig,
EPerson user) throws SQLException, AuthorizeException, IOException {
if (claimedTaskService.find(c, wfi, step.getId(), actionConfig.getId()) != null) {
workflowRequirementsService.addClaimedUser(c, wfi, step, c.getCurrentUser());
workflowRequirementsService.addClaimedUser(c, wfi, step, user);
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.createOwnedTask(c, wfi, step, actionConfig, user);
}

View File

@@ -138,6 +138,10 @@ public class ClaimAction extends UserSelectionAction {
RoleMembers roleMembers = role.getMembers(context, wfi);
ArrayList<EPerson> epersons = roleMembers.getAllUniqueMembers(context);
if (epersons.isEmpty() || step.getRequiredUsers() > epersons.size()) {
log.warn(String.format("There must be at least %s ePerson(s) in the group",
step.getRequiredUsers()));
}
return !(epersons.isEmpty() || step.getRequiredUsers() > epersons.size());
} else {
// We don't have a role and do have a UI so throw a workflow exception

View File

@@ -15,9 +15,12 @@
<bean id="selectrevieweractionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction" scope="prototype">
<property name="role" ref="scoreassignedreviewer"/>
</bean>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype"/>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype">
<property name="descriptionRequired" value="true"/>
<property name="maxValue" value="5"/>
</bean>
<bean id="evaluationactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreEvaluationAction" scope="prototype">
<property name="minimumAcceptanceScore" value="50" />
<property name="minimumAcceptanceScore" value="3" />
</bean>
@@ -63,6 +66,12 @@
<property name="requiresUI" value="true"/>
</bean>
<bean id="ratingreviewaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="ratingreviewaction"/>
<property name="processingAction" ref="ratingreviewactionAPI" />
<property name="requiresUI" value="true"/>
</bean>
<!--Autmatic step that evaluates scores (workflow.score) and checks if they match the configured minimum for archiving -->
<bean id="evaluationaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="evaluationaction"/>

View File

@@ -153,6 +153,7 @@
<bean id="scoreassignedreviewer" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).ITEM}"/>
<property name="name" value="Reviewer"/>
<property name="deleteTemporaryGroup" value="true"/>
</bean>

View File

@@ -9,11 +9,13 @@ package org.dspace.xmlworkflow;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
@@ -21,17 +23,24 @@ import org.dspace.builder.ClaimedTaskBuilder;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.discovery.IndexingService;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.service.XmlWorkflowService;
import org.dspace.xmlworkflow.state.Workflow;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.junit.After;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -47,6 +56,22 @@ public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase {
.getServiceByName(IndexingService.class.getName(),
IndexingService.class);
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
/**
* Cleans up the created workflow role groups after each test
* @throws SQLException
* @throws AuthorizeException
* @throws IOException
*/
@After
public void cleanup() throws SQLException, AuthorizeException, IOException {
Group reviewManagers = groupService.findByName(context, "ReviewManagers");
if (reviewManagers != null) {
groupService.delete(context, reviewManagers);
}
}
/**
* Test to verify that if a user submits an item into the workflow, then it gets rejected that the submitter gets
@@ -85,6 +110,93 @@ public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase {
assertTrue(this.containsRPForUser(taskToReject.getWorkflowItem().getItem(), submitter, Constants.WRITE));
}
/**
* Test to verify that if a user submits an item into the workflow, a reviewmanager can select a single reviewer
* eperson
*/
@Test
public void workflowUserSingleSelectedReviewer_ItemShouldBeEditable() throws Exception {
context.turnOffAuthorisationSystem();
EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build();
context.setCurrentUser(submitter);
EPerson reviewManager =
EPersonBuilder.createEPerson(context).withEmail("reviewmanager-test@example.org").build();
Community community = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection colWithWorkflow = CollectionBuilder.createCollection(context, community, "123456789/workflow-test-1")
.withName("Collection WITH workflow")
.withWorkflowGroup("reviewmanagers", reviewManager)
.build();
Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow);
ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager)
.withTitle("Test workflow item to reject").build();
// Set reviewer group property and add reviewer to group
SelectReviewerAction.resetGroup();
configurationService.setProperty("action.selectrevieweraction.group", "Reviewers");
Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build();
EPerson reviewer = EPersonBuilder.createEPerson(context).withEmail("reviewer@example.org").build();
groupService.addMember(context, reviewerGroup, reviewer);
context.restoreAuthSystemState();
// Review Manager should have access to workflow item
assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewManager, Constants.WRITE));
// select 1 reviewer
MockHttpServletRequest httpSelectReviewerRequest = new MockHttpServletRequest();
httpSelectReviewerRequest.setParameter("submit_select_reviewer", "true");
httpSelectReviewerRequest.setParameter("eperson", reviewer.getID().toString());
executeWorkflowAction(httpSelectReviewerRequest, workflow, task);
// Reviewer should have access to workflow item
assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer, Constants.WRITE));
}
/**
* Test to verify that if a user submits an item into the workflow, a reviewmanager can select a multiple reviewer
* epersons
*/
@Test
public void workflowUserMultipleSelectedReviewer_ItemShouldBeEditable() throws Exception {
context.turnOffAuthorisationSystem();
EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build();
context.setCurrentUser(submitter);
EPerson reviewManager =
EPersonBuilder.createEPerson(context).withEmail("reviewmanager-test@example.org").build();
Community community = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection colWithWorkflow = CollectionBuilder.createCollection(context, community, "123456789/workflow-test-1")
.withName("Collection WITH workflow")
.withWorkflowGroup("reviewmanagers", reviewManager)
.build();
Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow);
ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager)
.withTitle("Test workflow item to reject").build();
// Set reviewer group property and add reviewer to group
SelectReviewerAction.resetGroup();
configurationService.setProperty("action.selectrevieweraction.group", "Reviewers");
Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build();
EPerson reviewer1 = EPersonBuilder.createEPerson(context).withEmail("reviewer1@example.org").build();
EPerson reviewer2 = EPersonBuilder.createEPerson(context).withEmail("reviewer2@example.org").build();
groupService.addMember(context, reviewerGroup, reviewer1);
groupService.addMember(context, reviewerGroup, reviewer2);
context.restoreAuthSystemState();
// Review Manager should have access to workflow item
assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewManager, Constants.WRITE));
// Select multiple reviewers
MockHttpServletRequest httpSelectMultipleReviewers = new MockHttpServletRequest();
httpSelectMultipleReviewers.setParameter("submit_select_reviewer", "true");
httpSelectMultipleReviewers.setParameter("eperson", reviewer1.getID().toString(), reviewer2.getID().toString());
executeWorkflowAction(httpSelectMultipleReviewers, workflow, task);
// Reviewers should have access to workflow item
assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer1, Constants.WRITE));
assertTrue(this.containsRPForUser(task.getWorkflowItem().getItem(), reviewer2, Constants.WRITE));
}
private boolean containsRPForUser(Item item, EPerson user, int action) throws SQLException {
List<ResourcePolicy> rps = authorizeService.getPolicies(context, item);
for (ResourcePolicy rp : rps) {

View File

@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.ScoreReviewActionAdvancedInfoRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo;
/**
* This converter is responsible for transforming the model representation of a ScoreReviewActionAdvancedInfo to
* the REST representation of a ScoreReviewActionAdvancedInfo
*/
public class ScoreReviewActionAdvancedInfoConverter
implements DSpaceConverter<ScoreReviewActionAdvancedInfo, ScoreReviewActionAdvancedInfoRest> {
@Override
public ScoreReviewActionAdvancedInfoRest convert(ScoreReviewActionAdvancedInfo modelObject,
Projection projection) {
ScoreReviewActionAdvancedInfoRest restModel = new ScoreReviewActionAdvancedInfoRest();
restModel.setDescriptionRequired(modelObject.isDescriptionRequired());
restModel.setMaxValue(modelObject.getMaxValue());
restModel.setType(modelObject.getType());
restModel.setId(modelObject.getId());
return restModel;
}
@Override
public Class<ScoreReviewActionAdvancedInfo> getModelClass() {
return ScoreReviewActionAdvancedInfo.class;
}
}

View File

@@ -0,0 +1,35 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.SelectReviewerActionAdvancedInfoRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo;
/**
* This converter is responsible for transforming the model representation of a SelectReviewerActionAdvancedInfo to
* the REST representation of a SelectReviewerActionAdvancedInfo
*/
public class SelectReviewerActionAdvancedInfoConverter
implements DSpaceConverter<SelectReviewerActionAdvancedInfo, SelectReviewerActionAdvancedInfoRest> {
@Override
public SelectReviewerActionAdvancedInfoRest convert(SelectReviewerActionAdvancedInfo modelObject,
Projection projection) {
SelectReviewerActionAdvancedInfoRest restModel = new SelectReviewerActionAdvancedInfoRest();
restModel.setGroup(modelObject.getGroup());
restModel.setType(modelObject.getType());
restModel.setId(modelObject.getId());
return restModel;
}
@Override
public Class<SelectReviewerActionAdvancedInfo> getModelClass() {
return SelectReviewerActionAdvancedInfo.class;
}
}

View File

@@ -26,6 +26,10 @@ public class WorkflowActionConverter implements DSpaceConverter<WorkflowActionCo
restModel.setProjection(projection);
restModel.setId(modelObject.getId());
restModel.setOptions(modelObject.getOptions());
if (modelObject.isAdvanced()) {
restModel.setAdvancedOptions(modelObject.getAdvancedOptions());
restModel.setAdvancedInfo(modelObject.getAdvancedInfo());
}
return restModel;
}

View File

@@ -0,0 +1,37 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
/**
* Abstract class for {@link ActionAdvancedInfo}
*
* @author Marie Verdonck (Atmire) on 03/02/23
*/
public abstract class AdvancedInfoRest {
String id;
String type;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,35 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
/**
* The ScoreReviewActionAdvancedInfo REST Resource,
* see {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo}
*/
public class ScoreReviewActionAdvancedInfoRest extends AdvancedInfoRest {
private boolean descriptionRequired;
private int maxValue;
public boolean isDescriptionRequired() {
return descriptionRequired;
}
public void setDescriptionRequired(boolean descriptionRequired) {
this.descriptionRequired = descriptionRequired;
}
public int getMaxValue() {
return maxValue;
}
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
}

View File

@@ -0,0 +1,25 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
/**
* The SelectReviewerActionAdvancedInfoRest REST Resource,
* see {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo}
*/
public class SelectReviewerActionAdvancedInfoRest extends AdvancedInfoRest {
private String groupId;
public String getGroup() {
return groupId;
}
public void setGroup(String groupId) {
this.groupId = groupId;
}
}

View File

@@ -9,7 +9,10 @@ package org.dspace.app.rest.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.apache.commons.collections4.CollectionUtils;
import org.dspace.app.rest.RestResourceController;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
/**
* The rest resource used for workflow actions
@@ -23,6 +26,8 @@ public class WorkflowActionRest extends BaseObjectRest<String> {
public static final String NAME_PLURAL = "workflowactions";
private List<String> options;
private List<String> advancedOptions;
private List<ActionAdvancedInfo> advancedInfo;
@Override
public String getCategory() {
@@ -39,21 +44,33 @@ public class WorkflowActionRest extends BaseObjectRest<String> {
return NAME;
}
/**
* Generic getter for the options
*
* @return the options value of this WorkflowActionRest
*/
public List<String> getOptions() {
return options;
}
/**
* Generic setter for the options
*
* @param options The options to be set on this WorkflowActionRest
*/
public void setOptions(List<String> options) {
this.options = options;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public List<String> getAdvancedOptions() {
return advancedOptions;
}
public void setAdvancedOptions(List<String> advancedOptions) {
this.advancedOptions = advancedOptions;
}
public boolean getAdvanced() {
return CollectionUtils.isNotEmpty(getAdvancedOptions());
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public List<ActionAdvancedInfo> getAdvancedInfo() {
return advancedInfo;
}
public void setAdvancedInfo(List<ActionAdvancedInfo> advancedInfo) {
this.advancedInfo = advancedInfo;
}
}

View File

@@ -44,6 +44,7 @@ import org.dspace.builder.ClaimedTaskBuilder;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.PoolTaskBuilder;
import org.dspace.builder.WorkflowItemBuilder;
@@ -52,11 +53,11 @@ import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -78,6 +79,9 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest {
@Autowired
private XmlWorkflowFactory xmlWorkflowFactory;
@Autowired
GroupService groupService;
@Test
/**
* Retrieve a specific pooltask
@@ -4174,9 +4178,6 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest {
@Test
public void addReviewerToRunningWorkflowTest() throws Exception {
GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
context.turnOffAuthorisationSystem();
EPerson reviewer1 = EPersonBuilder.createEPerson(context)
@@ -4329,4 +4330,384 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest {
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
}
/**
* Test the run of the selectSingleReviewer workflow
* - Creates ReviewManagers and Reviewers, each with some members
* - Creates a normal user, not member of either group, this user is set on context
* - Tests selecting a single reviewer, multiple reviewers and selecting a non-reviewer
*
* @throws Exception
*/
@Test
public void selectReviewerWorkflowTest() throws Exception {
context.turnOffAuthorisationSystem();
// Create normal user, not member of "ReviewManagers" or "Reviewers" and set as current user
EPerson user = EPersonBuilder.createEPerson(context)
.withEmail("user@example.com")
.withPassword(password).build();
context.setCurrentUser(user);
// Create creator as this user is member of "ReviewManagers" for this item
EPerson creator = EPersonBuilder.createEPerson(context)
.withEmail("creator@example.com")
.withPassword(password).build();
// Create with some members to be added to "ReviewManagers"
EPerson reviewManager1 = EPersonBuilder.createEPerson(context)
.withEmail("reviewManager1@example.com")
.withPassword(password).build();
EPerson reviewManager2 = EPersonBuilder.createEPerson(context)
.withEmail("reviewManager2@example.com")
.withPassword(password).build();
EPerson reviewManager3 = EPersonBuilder.createEPerson(context)
.withEmail("reviewManager3@example.com")
.withPassword(password).build();
// The "selectSingleReviewer" requires the "ReviewManagers" repository group to be present with at least 1
// member
GroupBuilder.createGroup(context)
.withName("ReviewManagers")
.addMember(reviewManager1)
.addMember(reviewManager2)
.addMember(reviewManager3)
.build();
// Create "Reviewers" with some members
Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build();
EPerson reviewer1 = EPersonBuilder.createEPerson(context)
.withEmail("reviewer1@example.com")
.withPassword(password)
.withGroupMembership(reviewerGroup).build();
EPerson reviewer2 = EPersonBuilder.createEPerson(context)
.withEmail("reviewer2@example.com")
.withPassword(password)
.withGroupMembership(reviewerGroup).build();
EPerson reviewer3 = EPersonBuilder.createEPerson(context)
.withEmail("reviewer3@example.com")
.withPassword(password)
.withGroupMembership(reviewerGroup).build();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community").build();
// Create collection with handle "123456789/workflow-test-1" to use "selectSingleReviewer"
Collection collection =
CollectionBuilder.createCollection(context, parentCommunity, "123456789/workflow-test-1")
.withName("Collection 1")
.build();
// Create 3 pool tasks
// First one for selecting a single reviewer
PoolTask poolTask1 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager1)
.withTitle("Workflow Item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withAuthor("Doe, John")
.withSubject("ExtraEntry").build();
XmlWorkflowItem witem1 = poolTask1.getWorkflowItem();
// Second one for selecting multiple reviewers
PoolTask poolTask2 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager2)
.withTitle("Workflow Item 2")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withAuthor("Doe, John")
.withSubject("ExtraEntry").build();
XmlWorkflowItem witem2 = poolTask2.getWorkflowItem();
// Third one for trying to add user not in "Reviewers" group
PoolTask poolTask3 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager3)
.withTitle("Workflow Item 3")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withAuthor("Doe, John")
.withSubject("ExtraEntry").build();
XmlWorkflowItem witem3 = poolTask3.getWorkflowItem();
context.restoreAuthSystemState();
String reviewManager1Token = getAuthToken(reviewManager1.getEmail(), password);
String reviewManager2Token = getAuthToken(reviewManager2.getEmail(), password);
String reviewManager3Token = getAuthToken(reviewManager3.getEmail(), password);
String reviewer1Token = getAuthToken(reviewer1.getEmail(), password);
String reviewer2Token = getAuthToken(reviewer2.getEmail(), password);
String reviewer3Token = getAuthToken(reviewer3.getEmail(), password);
String adminToken = getAuthToken(admin.getEmail(), password);
String userToken = getAuthToken(user.getEmail(), password);
AtomicReference<Integer> idRef = new AtomicReference<>();
// Verify as member of "ReviewManagers" you can find these pool tasks
getClient(reviewManager1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(PoolTaskMatcher.matchPoolTask(poolTask1, "selectReviewerStep"))));
// Verify as member of "Reviewers" you can not find these pool tasks
getClient(reviewer1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID()))
.andExpect(status().isForbidden());
// Verify as member of "ReviewManagers" you can claim in this tasks
getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks")
.contentType(
MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE))
.content("/api/workflow/pooltasks/" + poolTask1.getID()))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask")))))
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
// Verify that pool task 1 no longer exists
getClient(reviewManager1Token).perform(get("/api/workflow/pooltasks/" + poolTask1.getID()))
.andExpect(status().isNotFound());
// Verify items now in claimed tasks /api/workflow/claimedtasks for user reviewManager1
getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager1.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// Verify items now not in claimed tasks /api/workflow/claimedtasks for user reviewer1
getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Test for single reviewer
SelectReviewerAction.resetGroup();
// Select reviewer1 as a reviewer, wf step 1
getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get())
.param("submit_select_reviewer", "true")
.param("eperson", reviewer1.getID().toString())
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isNoContent());
// Verify reviewer1 has the claimed task
getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// Verify other members of "Reviewers" don't have this task claimed
getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer2.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer3.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Verify members of "ReviewManagers" don't have this task claimed
getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager2.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager3.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Verify other users don't have this task claimed
getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", user.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Test for multiple reviewers
// Claim pooltask2 as member of "ReviewManagers"
getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks")
.contentType(
MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE))
.content("/api/workflow/pooltasks/" + poolTask2.getID()))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask")))))
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
// Select reviewer2 and reviewer3 as reviewers, wf step 1
getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks/" + idRef.get())
.param("submit_select_reviewer", "true")
.param("eperson", reviewer2.getID().toString())
.param("eperson", reviewer3.getID().toString())
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isNoContent());
// Verify reviewer2 has the claimed task
getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer2.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer2.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// Verify reviewer3 has the claimed task too
getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer3.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer3.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// Verify reviewer1 of "Reviewers" doesn't have this task claimed, only the first task
getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewer1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))),
hasJsonPath("$._embedded.workflowitem",
Matchers.not(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
// Verify members of "ReviewManagers" don't have this task claimed
getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager1.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager2.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager3.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Verify other users don't have this task claimed
getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", user.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Test to assign non-reviewer
// Claim pooltask3 as member of "ReviewManagers"
getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks")
.contentType(
MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE))
.content("/api/workflow/pooltasks/" + poolTask3.getID()))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask")))))
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
// Select (non-reviewer) user as a reviewer, wf step 1
getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get())
.param("submit_select_reviewer", "true")
.param("eperson", user.getID().toString())
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isNoContent());
// Verify user does not have this task claimed
getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", user.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(0)));
// Verify item still in claimed tasks for user reviewManager3 on step "selectrevieweraction"
getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser")
.param("uuid", reviewManager3.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains(
Matchers.allOf(
hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")),
hasJsonPath("$.type", Matchers.is("claimedtask")),
hasJsonPath("$._embedded.owner",
Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager3.getEmail()))),
hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")),
hasJsonPath("$._embedded.workflowitem",
Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(
witem3, "Workflow Item 3", "2017-10-17", "ExtraEntry")))
))))
.andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks")))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
}
}

View File

@@ -7,7 +7,9 @@
*/
package org.dspace.app.rest;
import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.SUBMIT_SCORE;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -17,9 +19,18 @@ import org.dspace.app.rest.matcher.WorkflowActionMatcher;
import org.dspace.app.rest.model.WorkflowActionRest;
import org.dspace.app.rest.repository.WorkflowActionRestRepository;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.GroupBuilder;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo;
import org.hamcrest.Matchers;
import org.junit.Test;
@@ -31,6 +42,8 @@ import org.junit.Test;
public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegrationTest {
private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory();
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
private static final String WORKFLOW_ACTIONS_ENDPOINT
= "/api/" + WorkflowActionRest.CATEGORY + "/" + WorkflowActionRest.NAME_PLURAL;
@@ -82,6 +95,7 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio
.andExpect(status().isOk())
// has options
.andExpect(jsonPath("$.options", not(empty())))
.andExpect(jsonPath("$.advanced", is(false)))
//Matches expected corresponding rest action values
.andExpect(jsonPath("$", Matchers.is(
WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow)
@@ -99,6 +113,7 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio
.andExpect(status().isOk())
// has no options
.andExpect(jsonPath("$.options", empty()))
.andExpect(jsonPath("$.advanced", is(false)))
//Matches expected corresponding rest action values
.andExpect(jsonPath("$", Matchers.is(
WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflowNoOptions)
@@ -125,4 +140,68 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegratio
//We expect a 401 Unauthorized
.andExpect(status().isUnauthorized());
}
@Test
public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
String nameActionWithOptions = "scorereviewaction";
WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions);
// create ScoreReviewActionAdvancedInfo to compare with output
ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo();
scoreReviewActionAdvancedInfo.setDescriptionRequired(true);
scoreReviewActionAdvancedInfo.setMaxValue(5);
scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE);
scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE);
//When we call this facets endpoint
getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions))
//We expect a 200 is ok status
.andExpect(status().isOk())
// has options
.andExpect(jsonPath("$.options", not(empty())))
.andExpect(jsonPath("$.advancedOptions", not(empty())))
.andExpect(jsonPath("$.advanced", is(true)))
.andExpect(jsonPath("$.advancedInfo", Matchers.contains(
WorkflowActionMatcher.matchScoreReviewActionAdvancedInfo(scoreReviewActionAdvancedInfo))))
//Matches expected corresponding rest action values
.andExpect(jsonPath("$", Matchers.is(
WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow)
)));
}
@Test
public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
String nameActionWithOptions = "selectrevieweraction";
// create reviewers group
SelectReviewerAction.resetGroup();
context.turnOffAuthorisationSystem();
Group group = GroupBuilder.createGroup(context).withName("ReviewersUUIDConfig").build();
configurationService.setProperty("action.selectrevieweraction.group", group.getID());
context.restoreAuthSystemState();
// create SelectReviewerActionAdvancedInfo to compare with output
SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo();
selectReviewerActionAdvancedInfo.setGroup(group.getID().toString());
selectReviewerActionAdvancedInfo.setType("submit_select_reviewer");
selectReviewerActionAdvancedInfo.generateId("submit_select_reviewer");
WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions);
//When we call this facets endpoint
getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions))
//We expect a 200 is ok status
.andExpect(status().isOk())
// has options
.andExpect(jsonPath("$.options", not(empty())))
.andExpect(jsonPath("$.advancedOptions", not(empty())))
.andExpect(jsonPath("$.advanced", is(true)))
.andExpect(jsonPath("$.advancedInfo", Matchers.contains(
WorkflowActionMatcher.matchSelectReviewerActionAdvancedInfo(selectReviewerActionAdvancedInfo))))
//Matches expected corresponding rest action values
.andExpect(jsonPath("$", Matchers.is(
WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow)
)));
}
}

View File

@@ -14,7 +14,10 @@ import static org.hamcrest.Matchers.is;
import org.dspace.app.rest.model.WorkflowActionRest;
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
/**
* @author Maria Verdonck (Atmire) on 06/01/2020
@@ -32,7 +35,35 @@ public class WorkflowActionMatcher {
return allOf(
hasJsonPath("$.id", is(workflowAction.getId())),
hasJsonPath("$.options", is(workflowAction.getOptions())),
hasJsonPath("$.advanced", is(workflowAction.isAdvanced())),
hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId()))
);
}
/**
* Matcher to check the contents of the advancedInfo for "ratingreviewaction"
* @param scoreReviewActionAdvancedInfo identical ScoreReviewActionAdvancedInfo object
*/
public static Matcher<? super Object> matchScoreReviewActionAdvancedInfo(
ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo) {
return Matchers.allOf(
hasJsonPath("$.descriptionRequired", is(scoreReviewActionAdvancedInfo.isDescriptionRequired())),
hasJsonPath("$.maxValue", is(scoreReviewActionAdvancedInfo.getMaxValue())),
hasJsonPath("$.type", is(scoreReviewActionAdvancedInfo.getType())),
hasJsonPath("$.id", is(scoreReviewActionAdvancedInfo.getId()))
);
}
/**
* Matcher to check the contents of the advancedInfo for "selectrevieweraction"
* @param selectReviewerActionAdvancedInfo identical SelectReviewerActionAdvancedInfo object
*/
public static Matcher<? super Object> matchSelectReviewerActionAdvancedInfo(
SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo) {
return Matchers.allOf(
hasJsonPath("$.group", is(selectReviewerActionAdvancedInfo.getGroup())),
hasJsonPath("$.type", is(selectReviewerActionAdvancedInfo.getType())),
hasJsonPath("$.id", is(selectReviewerActionAdvancedInfo.getId()))
);
}
}

View File

@@ -16,4 +16,9 @@
workflow.reviewer.file-edit=false
# Notify reviewers about tasks returned to the pool
#workflow.notify.returned.tasks = true
#workflow.notify.returned.tasks = true
# Reviewer group for the select reviewer workflow (can be UUID or group name)
# This determines the group from which reviewers can be chosen
# If this is not set, the review manager can choose reviewers from all e-people instead of this selected group
action.selectrevieweraction.group = Reviewers

View File

@@ -17,7 +17,13 @@
<dc-type>
<schema>workflow</schema>
<element>score</element>
<scope_note>Metadata field used for the score review</scope_note>
<scope_note>Metadata field used for the score review rating</scope_note>
</dc-type>
<dc-type>
<schema>workflow</schema>
<element>review</element>
<scope_note>Metadata field used for the score review description</scope_note>
</dc-type>
</dspace-dc-types>

View File

@@ -13,9 +13,12 @@
<bean id="selectrevieweractionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction" scope="prototype">
<property name="role" ref="scoreassignedreviewer"/>
</bean>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype"/>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype">
<property name="descriptionRequired" value="true"/>
<property name="maxValue" value="5"/>
</bean>
<bean id="evaluationactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreEvaluationAction" scope="prototype">
<property name="minimumAcceptanceScore" value="50" />
<property name="minimumAcceptanceScore" value="3" />
</bean>
@@ -61,6 +64,12 @@
<property name="requiresUI" value="true"/>
</bean>
<bean id="ratingreviewaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="ratingreviewaction"/>
<property name="processingAction" ref="ratingreviewactionAPI" />
<property name="requiresUI" value="true"/>
</bean>
<!--Autmatic step that evaluates scores (workflow.score) and checks if they match the configured minimum for archiving -->
<bean id="evaluationaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="evaluationaction"/>

View File

@@ -151,6 +151,7 @@
<bean id="scoreassignedreviewer" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).ITEM}"/>
<property name="name" value="Reviewer"/>
<property name="deleteTemporaryGroup" value="true"/>
</bean>