Merge pull request #10540 from atmire/fix-issue-10536_relation-field-requiredissue-main

Fix issue 10536 relation field required issue
This commit is contained in:
Tim Donohue
2025-09-12 14:05:07 -05:00
committed by GitHub
5 changed files with 324 additions and 25 deletions

View File

@@ -30,6 +30,7 @@
<name-map collection-entity-type="CustomEntityType" submission-name="entitytypetest"/>
<name-map collection-handle="123456789/test-duplicate-detection" submission-name="test-duplicate-detection"/>
<name-map collection-handle="123456789/collection-test-patch" submission-name="publicationTestPatch"/>
<name-map collection-handle="123456789/enforced-relation" submission-name="enforcedRelation"/>
</submission-map>
@@ -200,6 +201,13 @@
<processing-class>org.dspace.app.rest.submit.step.UploadStep</processing-class>
<type>upload</type>
</step-definition>
<step-definition id="publicationStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step-definition>
</step-definitions>
<!-- The submission-definitions map lays out the detailed definition of -->
@@ -305,6 +313,10 @@
<step id="traditionalpageone"/>
</submission-process>
<submission-process name="enforcedRelation">
<step id="publicationStep"></step>
</submission-process>
<submission-process name="publicationTestPatch">
<step id="collection" />
<step id="traditionalpageone" />

View File

@@ -465,6 +465,24 @@ it, please enter the types and the actual numbers or codes.</hint>
</row>
</form>
<form name="publicationStep">
<row>
<relation-field>
<relationship-type>isAuthorOfPublication</relationship-type>
<search-configuration>person</search-configuration>
<repeatable>true</repeatable>
<label>Author</label>
<hint>Enter the author's name (Family name, Given names).</hint>
<externalsources>orcid</externalsources>
<required>true</required>
<!-- You may choose to validate author names via a Regular Expression if it's appropriate for
your institution. The below regex requires a comma to be present in the author field.
However, this is disabled by default to support organizations as authors, etc. -->
<!--<regex>\w+(,)+\w+</regex>-->
</relation-field>
</row>
</form>
</form-definitions>

View File

@@ -23,9 +23,15 @@ import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionStepConfig;
import org.dspace.content.InProgressSubmission;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.RelationshipType;
import org.dspace.content.authority.service.MetadataAuthorityService;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.RelationshipService;
import org.dspace.content.service.RelationshipTypeService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
/**
@@ -54,6 +60,11 @@ public class MetadataValidation extends AbstractValidation {
private ConfigurationService configurationService;
private RelationshipTypeService relationshipTypeService =
ContentServiceFactory.getInstance().getRelationshipTypeService();
private RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
@Override
public List<ErrorRest> validate(SubmissionService submissionService, InProgressSubmission obj,
SubmissionStepConfig config) throws DCInputsReaderException, SQLException {
@@ -147,11 +158,42 @@ public class MetadataValidation extends AbstractValidation {
}
}
}
relationshipRequiredFieldCheck(ContextUtil.obtainCurrentRequestContext(), obj.getItem(), input, errors,
config);
}
}
return errors;
}
/**
* Checks if the relation type exists on the item. If not, sets the error state.
*
* @param context the current context
* @param item item in the submission
* @param input input field
* @param errors List holding all errors
* @param config submission step config
* @throws SQLException
*/
private void relationshipRequiredFieldCheck(Context context, Item item, DCInput input, List<ErrorRest> errors,
SubmissionStepConfig config) throws SQLException {
if (input.isRelationshipField() && input.isRequired()) {
String relationshipType = input.getRelationshipType();
if (itemService.getMetadataByMetadataString(item, "relation." + relationshipType).isEmpty()) {
List<RelationshipType> relationTypes =
relationshipTypeService.findByLeftwardOrRightwardTypeName(context, relationshipType);
for (RelationshipType relationType : relationTypes) {
if (!relationshipService.findByItemAndRelationshipType(context, item, relationType).isEmpty()) {
return;
}
}
addError(errors, ERROR_VALIDATION_REQUIRED,
"/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" +
input.getFieldName());
}
}
}
private void validateMetadataValues(List<MetadataValue> mdv, DCInput input, SubmissionStepConfig config,
boolean isAuthorityControlled, String fieldKey,

View File

@@ -68,13 +68,13 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(content().contentType(contentType))
//The configuration file for the test env includes 6 forms
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(
jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms")))
//The array of submissionforms should have a size of 8
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(10))))
//The array of submissionforms should have a size of 11
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(11))))
;
}
@@ -85,12 +85,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL
+ "config/submissionforms")))
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(10))));
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(11))));
}
@Test
@@ -697,10 +697,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
Matchers.containsString("page=1"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalPages", equalTo(5)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(0)));
getClient(tokenAdmin).perform(get("/api/config/submissionforms")
@@ -724,10 +724,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
Matchers.containsString("page=2"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalPages", equalTo(5)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(1)));
getClient(tokenAdmin).perform(get("/api/config/submissionforms")
@@ -735,8 +735,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.param("page", "2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("test-outside-submission-hidden")))
.andExpect(jsonPath("$._embedded.submissionforms[1].id", is("qualdroptest")))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("publicationStep")))
.andExpect(jsonPath("$._embedded.submissionforms[1].id", is("test-outside-submission-hidden")))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=0"), Matchers.containsString("size=2"))))
@@ -748,10 +748,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
Matchers.containsString("page=2"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalPages", equalTo(5)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(2)));
getClient(tokenAdmin).perform(get("/api/config/submissionforms")
@@ -759,8 +759,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.param("page", "3"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpagetwo")))
.andExpect(jsonPath("$._embedded.submissionforms[1].id", is("sampleauthority")))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("qualdroptest")))
.andExpect(jsonPath("$._embedded.submissionforms[1].id", is("traditionalpagetwo")))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=0"), Matchers.containsString("size=2"))))
@@ -772,10 +772,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
Matchers.containsString("page=3"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalPages", equalTo(5)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(3)));
getClient(tokenAdmin).perform(get("/api/config/submissionforms")
@@ -783,7 +783,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.param("page", "4"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpageone")))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("sampleauthority")))
.andExpect(jsonPath("$._embedded.submissionforms[1].id", is("traditionalpageone")))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=0"), Matchers.containsString("size=2"))))
@@ -795,10 +796,33 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(10)))
.andExpect(jsonPath("$.page.totalPages", equalTo(5)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(4)));
getClient(tokenAdmin).perform(get("/api/config/submissionforms")
.param("size", "2")
.param("page", "5"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.submissionforms[0].id", is("typebindtest")))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=0"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.prev.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=4"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.self.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/config/submissionforms?"),
Matchers.containsString("page=5"), Matchers.containsString("size=2"))))
.andExpect(jsonPath("$.page.size", is(2)))
.andExpect(jsonPath("$.page.totalElements", equalTo(11)))
.andExpect(jsonPath("$.page.totalPages", equalTo(6)))
.andExpect(jsonPath("$.page.number", is(5)));
}
}

View File

@@ -88,8 +88,10 @@ import org.dspace.content.MetadataFieldName;
import org.dspace.content.Relationship;
import org.dspace.content.RelationshipType;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.RelationshipService;
import org.dspace.core.Constants;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
@@ -9808,4 +9810,205 @@ ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup)
WorkspaceItemBuilder.deleteWorkspaceItem(idRef.get());
}
}
@Test
public void enforceRequiredRelationTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build();
EntityType person = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build();
RelationshipType isAuthorOfPublication = RelationshipTypeBuilder
.createRelationshipTypeBuilder(context, publication, person, "isAuthorOfPublication",
"isPublicationOfAuthor", 1, null, 0,
null).withCopyToLeft(false).withCopyToRight(true).build();
isAuthorOfPublication.setTilted(RelationshipType.Tilted.NONE);
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
.withEntityType("Person").build();
Collection col2 = CollectionBuilder.createCollection(context, parentCommunity, "123456789/enforced-relation")
.withName("Collection 2")
.withEntityType("Publication").build();
Item author = ItemBuilder.createItem(context, col1)
.withTitle("Author1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withPersonIdentifierLastName("Smith")
.withPersonIdentifierFirstName("Donald")
.build();
// two workspace items. Only one of them has the required relationship.
WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
WorkspaceItem workspaceItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
Relationship relationship1 = relationshipService.create(
context,
workspaceItem.getItem(),
author,
isAuthorOfPublication,
0, 0,
"isAuthorOfPublication",
"isPublicationOfAuthor"
);
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
// try to deposit the items. One should fail
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem.getID())
.contentType(textUriContentType))
.andExpect(status().isCreated());
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem2.getID())
.contentType(textUriContentType))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void enforceRequiredRelationTiltedRightTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build();
EntityType person = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build();
RelationshipType isAuthorOfPublication = RelationshipTypeBuilder
.createRelationshipTypeBuilder(context, publication, person, "isAuthorOfPublication",
"isPublicationOfAuthor", 1, null, 0,
null).withCopyToLeft(false).withCopyToRight(true).build();
isAuthorOfPublication.setTilted(RelationshipType.Tilted.RIGHT);
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
.withEntityType("Person").build();
Collection col2 = CollectionBuilder.createCollection(context, parentCommunity, "123456789/enforced-relation")
.withName("Collection 2")
.withEntityType("Publication").build();
Item author = ItemBuilder.createItem(context, col1)
.withTitle("Author1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withPersonIdentifierLastName("Smith")
.withPersonIdentifierFirstName("Donald")
.build();
// two workspace items. Only one of them has the required relationship.
WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
WorkspaceItem workspaceItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
Relationship relationship1 = relationshipService.create(
context,
workspaceItem.getItem(),
author,
isAuthorOfPublication,
0, 0,
"isAuthorOfPublication",
"isPublicationOfAuthor"
);
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
// try to deposit the items. One should fail
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem.getID())
.contentType(textUriContentType))
.andExpect(status().isCreated());
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem2.getID())
.contentType(textUriContentType))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void enforceRequiredRelationTiltedLeftTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build();
EntityType person = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build();
RelationshipType isAuthorOfPublication = RelationshipTypeBuilder
.createRelationshipTypeBuilder(context, publication, person, "isAuthorOfPublication",
"isPublicationOfAuthor", 1, null, 0,
null).withCopyToLeft(false).withCopyToRight(true).build();
isAuthorOfPublication.setTilted(RelationshipType.Tilted.LEFT);
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1")
.withEntityType("Person").build();
Collection col2 = CollectionBuilder.createCollection(context, parentCommunity, "123456789/enforced-relation")
.withName("Collection 2")
.withEntityType("Publication").build();
Item author = ItemBuilder.createItem(context, col1)
.withTitle("Author1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald")
.withPersonIdentifierLastName("Smith")
.withPersonIdentifierFirstName("Donald")
.build();
// two workspace items. Only one of them has the required relationship.
WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
WorkspaceItem workspaceItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2)
.withEntityType("Publication")
.build();
RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
Relationship relationship1 = relationshipService.create(
context,
workspaceItem.getItem(),
author,
isAuthorOfPublication,
0, 0,
"isAuthorOfPublication",
"isPublicationOfAuthor"
);
context.restoreAuthSystemState();
String adminToken = getAuthToken(admin.getEmail(), password);
// try to deposit the items. One should fail
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem.getID())
.contentType(textUriContentType))
.andExpect(status().isCreated());
getClient(adminToken).perform(post("/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + workspaceItem2.getID())
.contentType(textUriContentType))
.andExpect(status().isUnprocessableEntity());
}
}