Skip to content

Commit 987df46

Browse files
committed
Merge branch 'develop' into 10517-dataset-types #10517
2 parents 9c44b30 + 2e633c1 commit 987df46

File tree

6 files changed

+188
-40
lines changed

6 files changed

+188
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed dataverses/{identifier}/metadatablocks endpoint to not return fields marked as displayOnCreate=true if there is an input level with include=false, when query parameters returnDatasetFieldTypes=true and onlyDisplayedOnCreate=true are set.

doc/sphinx-guides/source/_templates/navbar.html

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">About <span class="caret"></span></a>
2626
<ul class="dropdown-menu">
2727
<li><a target="_blank" href="https://dataverse.org/about">About the Project</a></li>
28-
<li><a target="_blank" href="https://dataverse.org/add-data">Add Data</a></li>
2928
<li><a target="_blank" href="https://dataverse.org/blog">Blog</a></li>
3029
<li><a target="_blank" href="https://dataverse.org/presentations">Presentations</a></li>
3130
<li><a target="_blank" href="https://dataverse.org/publications">Publications</a></li>

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

+122
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import jakarta.persistence.PersistenceException;
4040
import jakarta.persistence.TypedQuery;
4141

42+
import jakarta.persistence.criteria.*;
4243
import org.apache.commons.codec.digest.DigestUtils;
4344
import org.apache.commons.httpclient.HttpException;
4445
import org.apache.commons.lang3.StringUtils;
@@ -851,4 +852,125 @@ public String getFieldLanguage(String languages, String localeCode) {
851852
}
852853
return null;
853854
}
855+
856+
public List<DatasetFieldType> findAllDisplayedOnCreateInMetadataBlock(MetadataBlock metadataBlock) {
857+
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
858+
CriteriaQuery<DatasetFieldType> criteriaQuery = criteriaBuilder.createQuery(DatasetFieldType.class);
859+
860+
Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
861+
Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
862+
863+
Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
864+
865+
criteriaQuery.where(
866+
criteriaBuilder.and(
867+
criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()),
868+
datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")),
869+
criteriaBuilder.or(
870+
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
871+
requiredInDataversePredicate
872+
)
873+
)
874+
);
875+
876+
criteriaQuery.select(datasetFieldTypeRoot).distinct(true);
877+
878+
TypedQuery<DatasetFieldType> typedQuery = em.createQuery(criteriaQuery);
879+
return typedQuery.getResultList();
880+
}
881+
882+
public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate) {
883+
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
884+
CriteriaQuery<DatasetFieldType> criteriaQuery = criteriaBuilder.createQuery(DatasetFieldType.class);
885+
886+
Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
887+
Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
888+
Root<Dataverse> dataverseRoot = criteriaQuery.from(Dataverse.class);
889+
890+
// Join Dataverse with DataverseFieldTypeInputLevel on the "dataverseFieldTypeInputLevels" attribute, using a LEFT JOIN.
891+
Join<Dataverse, DataverseFieldTypeInputLevel> datasetFieldTypeInputLevelJoin = dataverseRoot.join("dataverseFieldTypeInputLevels", JoinType.LEFT);
892+
893+
// Define a predicate to include DatasetFieldTypes that are marked as included in the input level.
894+
Predicate includedAsInputLevelPredicate = criteriaBuilder.and(
895+
criteriaBuilder.equal(datasetFieldTypeRoot, datasetFieldTypeInputLevelJoin.get("datasetFieldType")),
896+
criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("include"))
897+
);
898+
899+
// Define a predicate to include DatasetFieldTypes that are marked as required in the input level.
900+
Predicate requiredAsInputLevelPredicate = criteriaBuilder.and(
901+
criteriaBuilder.equal(datasetFieldTypeRoot, datasetFieldTypeInputLevelJoin.get("datasetFieldType")),
902+
criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("required"))
903+
);
904+
905+
// Create a subquery to check for the absence of a specific DataverseFieldTypeInputLevel.
906+
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
907+
Root<DataverseFieldTypeInputLevel> subqueryRoot = subquery.from(DataverseFieldTypeInputLevel.class);
908+
subquery.select(criteriaBuilder.literal(1L))
909+
.where(
910+
criteriaBuilder.equal(subqueryRoot.get("dataverse"), dataverseRoot),
911+
criteriaBuilder.equal(subqueryRoot.get("datasetFieldType"), datasetFieldTypeRoot)
912+
);
913+
914+
// Define a predicate to exclude DatasetFieldTypes that have no associated input level (i.e., the subquery does not return a result).
915+
Predicate hasNoInputLevelPredicate = criteriaBuilder.not(criteriaBuilder.exists(subquery));
916+
917+
// Define a predicate to include the required fields in Dataverse.
918+
Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
919+
920+
// Define a predicate for displaying DatasetFieldTypes on create.
921+
// If onlyDisplayedOnCreate is true, include fields that:
922+
// - Are either marked as displayed on create OR marked as required, OR
923+
// - Are required according to the input level.
924+
// Otherwise, use an always-true predicate (conjunction).
925+
Predicate displayedOnCreatePredicate = onlyDisplayedOnCreate
926+
? criteriaBuilder.or(
927+
criteriaBuilder.or(
928+
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
929+
requiredInDataversePredicate
930+
),
931+
requiredAsInputLevelPredicate
932+
)
933+
: criteriaBuilder.conjunction();
934+
935+
// Build the final WHERE clause by combining all the predicates.
936+
criteriaQuery.where(
937+
criteriaBuilder.equal(dataverseRoot.get("id"), dataverse.getId()), // Match the Dataverse ID.
938+
criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID.
939+
metadataBlockRoot.in(dataverseRoot.get("metadataBlocks")), // Ensure the MetadataBlock is part of the Dataverse.
940+
datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock.
941+
criteriaBuilder.or(includedAsInputLevelPredicate, hasNoInputLevelPredicate), // Include DatasetFieldTypes based on the input level predicates.
942+
displayedOnCreatePredicate // Apply the display-on-create filter if necessary.
943+
);
944+
945+
criteriaQuery.select(datasetFieldTypeRoot).distinct(true);
946+
947+
return em.createQuery(criteriaQuery).getResultList();
948+
}
949+
950+
private Predicate buildRequiredInDataversePredicate(CriteriaBuilder criteriaBuilder, Root<DatasetFieldType> datasetFieldTypeRoot) {
951+
// Predicate to check if the current DatasetFieldType is required.
952+
Predicate isRequired = criteriaBuilder.isTrue(datasetFieldTypeRoot.get("required"));
953+
954+
// Subquery to check if the parentDatasetFieldType is required or null.
955+
// We need this check to avoid including conditionally required fields.
956+
Subquery<Boolean> subquery = criteriaBuilder.createQuery(Boolean.class).subquery(Boolean.class);
957+
Root<DatasetFieldType> parentRoot = subquery.from(DatasetFieldType.class);
958+
959+
subquery.select(criteriaBuilder.literal(true))
960+
.where(
961+
criteriaBuilder.equal(parentRoot, datasetFieldTypeRoot.get("parentDatasetFieldType")),
962+
criteriaBuilder.or(
963+
criteriaBuilder.isNull(parentRoot.get("required")),
964+
criteriaBuilder.isTrue(parentRoot.get("required"))
965+
)
966+
);
967+
968+
// Predicate to check that either the parentDatasetFieldType meets the condition or doesn't exist (is null).
969+
Predicate parentCondition = criteriaBuilder.or(
970+
criteriaBuilder.exists(subquery),
971+
criteriaBuilder.isNull(datasetFieldTypeRoot.get("parentDatasetFieldType"))
972+
);
973+
974+
return criteriaBuilder.and(isRequired, parentCondition);
975+
}
854976
}

src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java

+17-22
Original file line numberDiff line numberDiff line change
@@ -633,31 +633,26 @@ public static JsonObjectBuilder json(MetadataBlock metadataBlock) {
633633
}
634634

635635
public static JsonObjectBuilder json(MetadataBlock metadataBlock, boolean printOnlyDisplayedOnCreateDatasetFieldTypes, Dataverse ownerDataverse) {
636-
JsonObjectBuilder jsonObjectBuilder = jsonObjectBuilder();
637-
jsonObjectBuilder.add("id", metadataBlock.getId());
638-
jsonObjectBuilder.add("name", metadataBlock.getName());
639-
jsonObjectBuilder.add("displayName", metadataBlock.getDisplayName());
640-
jsonObjectBuilder.add("displayOnCreate", metadataBlock.isDisplayOnCreate());
636+
JsonObjectBuilder jsonObjectBuilder = jsonObjectBuilder()
637+
.add("id", metadataBlock.getId())
638+
.add("name", metadataBlock.getName())
639+
.add("displayName", metadataBlock.getDisplayName())
640+
.add("displayOnCreate", metadataBlock.isDisplayOnCreate());
641641

642-
JsonObjectBuilder fieldsBuilder = Json.createObjectBuilder();
643-
Set<DatasetFieldType> datasetFieldTypes = new TreeSet<>(metadataBlock.getDatasetFieldTypes());
644-
645-
for (DatasetFieldType datasetFieldType : datasetFieldTypes) {
646-
Long datasetFieldTypeId = datasetFieldType.getId();
647-
boolean requiredAsInputLevelInOwnerDataverse = ownerDataverse != null && ownerDataverse.isDatasetFieldTypeRequiredAsInputLevel(datasetFieldTypeId);
648-
boolean includedAsInputLevelInOwnerDataverse = ownerDataverse != null && ownerDataverse.isDatasetFieldTypeIncludedAsInputLevel(datasetFieldTypeId);
649-
boolean isNotInputLevelInOwnerDataverse = ownerDataverse != null && !ownerDataverse.isDatasetFieldTypeInInputLevels(datasetFieldTypeId);
642+
Set<DatasetFieldType> datasetFieldTypes;
650643

651-
DatasetFieldType parentDatasetFieldType = datasetFieldType.getParentDatasetFieldType();
652-
boolean isRequired = parentDatasetFieldType == null ? datasetFieldType.isRequired() : parentDatasetFieldType.isRequired();
653-
654-
boolean displayCondition = printOnlyDisplayedOnCreateDatasetFieldTypes
655-
? (datasetFieldType.isDisplayOnCreate() || isRequired || requiredAsInputLevelInOwnerDataverse)
656-
: ownerDataverse == null || includedAsInputLevelInOwnerDataverse || isNotInputLevelInOwnerDataverse;
644+
if (ownerDataverse != null) {
645+
datasetFieldTypes = new TreeSet<>(datasetFieldService.findAllInMetadataBlockAndDataverse(
646+
metadataBlock, ownerDataverse, printOnlyDisplayedOnCreateDatasetFieldTypes));
647+
} else {
648+
datasetFieldTypes = printOnlyDisplayedOnCreateDatasetFieldTypes
649+
? new TreeSet<>(datasetFieldService.findAllDisplayedOnCreateInMetadataBlock(metadataBlock))
650+
: new TreeSet<>(metadataBlock.getDatasetFieldTypes());
651+
}
657652

658-
if (displayCondition) {
659-
fieldsBuilder.add(datasetFieldType.getName(), json(datasetFieldType, ownerDataverse));
660-
}
653+
JsonObjectBuilder fieldsBuilder = Json.createObjectBuilder();
654+
for (DatasetFieldType datasetFieldType : datasetFieldTypes) {
655+
fieldsBuilder.add(datasetFieldType.getName(), json(datasetFieldType, ownerDataverse));
661656
}
662657

663658
jsonObjectBuilder.add("fields", fieldsBuilder);

src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java

+43-16
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import static jakarta.ws.rs.core.Response.Status.*;
2727
import static org.hamcrest.CoreMatchers.*;
28-
import static org.hamcrest.CoreMatchers.containsString;
2928
import static org.hamcrest.MatcherAssert.assertThat;
3029
import static org.hamcrest.Matchers.hasItemInArray;
3130
import static org.junit.jupiter.api.Assertions.*;
@@ -702,9 +701,9 @@ public void testListMetadataBlocks() {
702701
Response setMetadataBlocksResponse = UtilIT.setMetadataBlocks(dataverseAlias, Json.createArrayBuilder().add("citation").add("astrophysics"), apiToken);
703702
setMetadataBlocksResponse.then().assertThat().statusCode(OK.getStatusCode());
704703

705-
String[] testInputLevelNames = {"geographicCoverage", "country", "city"};
706-
boolean[] testRequiredInputLevels = {false, true, false};
707-
boolean[] testIncludedInputLevels = {false, true, true};
704+
String[] testInputLevelNames = {"geographicCoverage", "country", "city", "notesText"};
705+
boolean[] testRequiredInputLevels = {false, true, false, false};
706+
boolean[] testIncludedInputLevels = {false, true, true, false};
708707
Response updateDataverseInputLevelsResponse = UtilIT.updateDataverseInputLevels(dataverseAlias, testInputLevelNames, testRequiredInputLevels, testIncludedInputLevels, apiToken);
709708
updateDataverseInputLevelsResponse.then().assertThat().statusCode(OK.getStatusCode());
710709

@@ -774,17 +773,22 @@ public void testListMetadataBlocks() {
774773
// Check dataset fields for the updated input levels are retrieved
775774
int geospatialMetadataBlockIndex = actualMetadataBlockDisplayName1.equals("Geospatial Metadata") ? 0 : actualMetadataBlockDisplayName2.equals("Geospatial Metadata") ? 1 : 2;
776775

776+
// Since the included property of notesText is set to false, we should retrieve the total number of fields minus one
777+
int citationMetadataBlockIndex = geospatialMetadataBlockIndex == 0 ? 1 : 0;
778+
listMetadataBlocksResponse.then().assertThat()
779+
.body(String.format("data[%d].fields.size()", citationMetadataBlockIndex), equalTo(78));
780+
777781
// Since the included property of geographicCoverage is set to false, we should retrieve the total number of fields minus one
778782
listMetadataBlocksResponse.then().assertThat()
779783
.body(String.format("data[%d].fields.size()", geospatialMetadataBlockIndex), equalTo(10));
780784

781-
String actualMetadataField1 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.geographicCoverage.name", geospatialMetadataBlockIndex));
782-
String actualMetadataField2 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.country.name", geospatialMetadataBlockIndex));
783-
String actualMetadataField3 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.city.name", geospatialMetadataBlockIndex));
785+
String actualGeospatialMetadataField1 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.geographicCoverage.name", geospatialMetadataBlockIndex));
786+
String actualGeospatialMetadataField2 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.country.name", geospatialMetadataBlockIndex));
787+
String actualGeospatialMetadataField3 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.city.name", geospatialMetadataBlockIndex));
784788

785-
assertNull(actualMetadataField1);
786-
assertNotNull(actualMetadataField2);
787-
assertNotNull(actualMetadataField3);
789+
assertNull(actualGeospatialMetadataField1);
790+
assertNotNull(actualGeospatialMetadataField2);
791+
assertNotNull(actualGeospatialMetadataField3);
788792

789793
// Existent dataverse and onlyDisplayedOnCreate=true and returnDatasetFieldTypes=true
790794
listMetadataBlocksResponse = UtilIT.listMetadataBlocks(dataverseAlias, true, true, apiToken);
@@ -807,13 +811,27 @@ public void testListMetadataBlocks() {
807811
listMetadataBlocksResponse.then().assertThat()
808812
.body(String.format("data[%d].fields.size()", geospatialMetadataBlockIndex), equalTo(1));
809813

810-
actualMetadataField1 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.geographicCoverage.name", geospatialMetadataBlockIndex));
811-
actualMetadataField2 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.country.name", geospatialMetadataBlockIndex));
812-
actualMetadataField3 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.city.name", geospatialMetadataBlockIndex));
814+
actualGeospatialMetadataField1 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.geographicCoverage.name", geospatialMetadataBlockIndex));
815+
actualGeospatialMetadataField2 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.country.name", geospatialMetadataBlockIndex));
816+
actualGeospatialMetadataField3 = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.city.name", geospatialMetadataBlockIndex));
817+
818+
assertNull(actualGeospatialMetadataField1);
819+
assertNotNull(actualGeospatialMetadataField2);
820+
assertNull(actualGeospatialMetadataField3);
821+
822+
citationMetadataBlockIndex = geospatialMetadataBlockIndex == 0 ? 1 : 0;
823+
824+
// notesText has displayOnCreate=true but has include=false, so should not be retrieved
825+
String notesTextCitationMetadataField = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.notesText.name", citationMetadataBlockIndex));
826+
assertNull(notesTextCitationMetadataField);
827+
828+
// producerName is a conditionally required field, so should not be retrieved
829+
String producerNameCitationMetadataField = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.producerName.name", citationMetadataBlockIndex));
830+
assertNull(producerNameCitationMetadataField);
813831

814-
assertNull(actualMetadataField1);
815-
assertNotNull(actualMetadataField2);
816-
assertNull(actualMetadataField3);
832+
// author is a required field, so should be retrieved
833+
String authorCitationMetadataField = listMetadataBlocksResponse.then().extract().path(String.format("data[%d].fields.author.name", citationMetadataBlockIndex));
834+
assertNotNull(authorCitationMetadataField);
817835

818836
// User has no permissions on the requested dataverse
819837
Response createSecondUserResponse = UtilIT.createRandomUser();
@@ -825,6 +843,15 @@ public void testListMetadataBlocks() {
825843

826844
listMetadataBlocksResponse = UtilIT.listMetadataBlocks(secondDataverseAlias, true, true, apiToken);
827845
listMetadataBlocksResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());
846+
847+
// List metadata blocks from Root
848+
listMetadataBlocksResponse = UtilIT.listMetadataBlocks("root", true, true, apiToken);
849+
listMetadataBlocksResponse.then().assertThat().statusCode(OK.getStatusCode());
850+
listMetadataBlocksResponse.then().assertThat()
851+
.statusCode(OK.getStatusCode())
852+
.body("data[0].displayName", equalTo("Citation Metadata"))
853+
.body("data[0].fields", not(equalTo(null)))
854+
.body("data.size()", equalTo(1));
828855
}
829856

830857
@Test

0 commit comments

Comments
 (0)