diff --git a/doc/release-notes/10519-dataset-types.md b/doc/release-notes/10519-dataset-types.md new file mode 100644 index 00000000000..99cf79a796f --- /dev/null +++ b/doc/release-notes/10519-dataset-types.md @@ -0,0 +1,12 @@ +## Dataset Types can be linked to Metadata Blocks + +Metadata blocks (e.g. "CodeMeta") can now be linked to dataset types (e.g. "software") using new superuser APIs. + +This will have the following effects for the APIs used by the new Dataverse UI ( https://github.com/IQSS/dataverse-frontend ): + +- The list of fields shown when creating a dataset will include fields marked as "displayoncreate" (in the tsv/database) for metadata blocks (e.g. "CodeMeta") that are linked to the dataset type (e.g. "software") that is passed to the API. +- The metadata blocks shown when editing a dataset will include metadata blocks (e.g. "CodeMeta") that are linked to the dataset type (e.g. "software") that is passed to the API. + +Mostly in order to write automated tests for the above, a [displayOnCreate](https://dataverse-guide--11001.org.readthedocs.build/en/11001/api/native-api.html#set-displayoncreate-for-a-dataset-field) API endpoint has been added. + +For more information, see the guides ([overview](https://dataverse-guide--11001.org.readthedocs.build/en/11001/user/dataset-management.html#dataset-types), [new APIs](https://dataverse-guide--11001.org.readthedocs.build/en/11001/api/native-api.html#link-dataset-type-with-metadata-blocks)), #10519 and #11001. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 160e68fd685..717633442c6 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -540,6 +540,8 @@ The fully expanded example above (without environment variables) looks like this curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/dataverses/root/assignments/6" +.. _list-metadata-blocks-for-a-collection: + List Metadata Blocks Defined on a Dataverse Collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -567,6 +569,7 @@ This endpoint supports the following optional query parameters: - ``returnDatasetFieldTypes``: Whether or not to return the dataset field types present in each metadata block. If not set, the default value is false. - ``onlyDisplayedOnCreate``: Whether or not to return only the metadata blocks that are displayed on dataset creation. If ``returnDatasetFieldTypes`` is true, only the dataset field types shown on dataset creation will be returned within each metadata block. If not set, the default value is false. +- ``datasetType``: Optionally return additional fields from metadata blocks that are linked with a particular dataset type (see :ref:`dataset-types` in the User Guide). Pass a single dataset type as a string. For a list of dataset types you can pass, see :ref:`api-list-dataset-types`. An example using the optional query parameters is presented below: @@ -575,14 +578,17 @@ An example using the optional query parameters is presented below: export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx export SERVER_URL=https://demo.dataverse.org export ID=root + export DATASET_TYPE=software - curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/dataverses/$ID/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/dataverses/$ID/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true&datasetType=$DATASET_TYPE" The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true" + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true&datasetType=software" + +.. _define-metadata-blocks-for-a-dataverse-collection: Define Metadata Blocks for a Dataverse Collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -609,6 +615,8 @@ The fully expanded example above (without environment variables) looks like this curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST -H "Content-type:application/json" --upload-file define-metadatablocks.json "https://demo.dataverse.org/api/dataverses/root/metadatablocks" +An alternative to defining metadata blocks at a collection level is to create and use a dataset type. See :ref:`api-link-dataset-type`. + Determine if a Dataverse Collection Inherits Its Metadata Blocks from Its Parent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3473,6 +3481,36 @@ The fully expanded example above (without environment variables) looks like this curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/datasets/datasetTypes/3" +.. _api-link-dataset-type: + +Link Dataset Type with Metadata Blocks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Linking a dataset type with one or more metadata blocks results in additional fields from those blocks appearing in the output from the :ref:`list-metadata-blocks-for-a-collection` API endpoint. The new frontend for Dataverse (https://github.com/IQSS/dataverse-frontend) uses the JSON output from this API endpoint to construct the page that users see when creating or editing a dataset. Once the frontend has been updated to pass in the dataset type (https://github.com/IQSS/dataverse-client-javascript/issues/210), specifying a dataset type in this way can be an alternative way to display additional metadata fields than the traditional method, which is to enable a metadata block at the collection level (see :ref:`define-metadata-blocks-for-a-dataverse-collection`). + +For example, a superuser could create a type called "software" and link it to the "CodeMeta" metadata block (this example is below). Then, once the new frontend allows it, the user can specify that they want to create a dataset of type software and see the additional metadata fields from the CodeMeta block when creating or editing their dataset. + +This API endpoint is for superusers only. + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export TYPE=software + export JSON='["codeMeta20"]' + + curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE" -X PUT -d $JSON + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Content-Type: application/json" "https://demo.dataverse.org/api/datasets/datasetTypes/software" -X PUT -d '["codeMeta20"]' + +To update the blocks that are linked, send an array with those blocks. + +To remove all links to blocks, send an empty array. + Files ----- @@ -5256,6 +5294,27 @@ The fully expanded example above (without environment variables) looks like this curl "https://demo.dataverse.org/api/datasetfields/facetables" +.. _setDisplayOnCreate: + +Set displayOnCreate for a Dataset Field +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Set displayOnCreate for a dataset field. See also :doc:`/admin/metadatacustomization` in the Admin Guide. + +.. code-block:: bash + + export SERVER_URL=http://localhost:8080 + export FIELD=subtitle + export BOOLEAN=true + + curl -X POST "$SERVER_URL/api/admin/datasetfield/setDisplayOnCreate?datasetFieldType=$FIELD&setDisplayOnCreate=$BOOLEAN" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -X POST "http://localhost:8080/api/admin/datasetfield/setDisplayOnCreate?datasetFieldType=studyAssayCellType&setDisplayOnCreate=true" + .. _Notifications: Notifications diff --git a/doc/sphinx-guides/source/user/dataset-management.rst b/doc/sphinx-guides/source/user/dataset-management.rst index b3820f034e7..9b8151801ec 100755 --- a/doc/sphinx-guides/source/user/dataset-management.rst +++ b/doc/sphinx-guides/source/user/dataset-management.rst @@ -801,13 +801,15 @@ If you deaccession the most recently published version of the dataset but not al Dataset Types ============= +.. note:: Development of the dataset types feature is ongoing. Please see https://github.com/IQSS/dataverse-pm/issues/307 for details. + Out of the box, all datasets have a dataset type of "dataset". Superusers can add additional types such as "software" or "workflow" using the :ref:`api-add-dataset-type` API endpoint. Once more than one type appears in search results, a facet called "Dataset Type" will appear allowing you to filter down to a certain type. If your installation is configured to use DataCite as a persistent ID (PID) provider, the appropriate type ("Dataset", "Software", "Workflow") will be sent to DataCite when the dataset is published for those three types. -Currently, the dataset type can only be specified via API and only when the dataset is created. For details, see the following sections of the API guide: +Currently, specifying a type for a dataset can only be done via API and only when the dataset is created. The type can't currently be changed afterward. For details, see the following sections of the API guide: - :ref:`api-create-dataset-with-type` (Native API) - :ref:`api-semantic-create-dataset-with-type` (Semantic API) @@ -815,7 +817,7 @@ Currently, the dataset type can only be specified via API and only when the data Dataset types can be listed, added, or deleted via API. See :ref:`api-dataset-types` in the API Guide for more. -Development of the dataset types feature is ongoing. Please see https://github.com/IQSS/dataverse/issues/10489 for details. +Dataset types can be linked with metadata blocks to make fields from those blocks available when datasets of that type are created or edited. See :ref:`api-link-dataset-type` and :ref:`list-metadata-blocks-for-a-collection` for details. .. |image1| image:: ./img/DatasetDiagram.png :class: img-responsive diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index ce181d27887..fdde14cdee5 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -90,6 +90,8 @@ services: - dev networks: - dataverse + volumes: + - ./docker-dev-volumes/solr/data:/var/solr dev_dv_initializer: container_name: "dev_dv_initializer" diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java index ded7c83de62..210cf383378 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse; +import edu.harvard.iq.dataverse.dataset.DatasetType; import java.io.IOException; import java.io.StringReader; import java.net.URI; @@ -871,7 +872,7 @@ public List findAllDisplayedOnCreateInMetadataBlock(MetadataBl Root metadataBlockRoot = criteriaQuery.from(MetadataBlock.class); Root datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class); - Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot); + Predicate fieldRequiredInTheInstallation = buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot); criteriaQuery.where( criteriaBuilder.and( @@ -879,7 +880,7 @@ public List findAllDisplayedOnCreateInMetadataBlock(MetadataBl datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), criteriaBuilder.or( criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")), - requiredInDataversePredicate + fieldRequiredInTheInstallation ) ) ); @@ -890,9 +891,9 @@ public List findAllDisplayedOnCreateInMetadataBlock(MetadataBl return typedQuery.getResultList(); } - public List findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate) { + public List findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate, DatasetType datasetType) { if (!dataverse.isMetadataBlockRoot() && dataverse.getOwner() != null) { - return findAllInMetadataBlockAndDataverse(metadataBlock, dataverse.getOwner(), onlyDisplayedOnCreate); + return findAllInMetadataBlockAndDataverse(metadataBlock, dataverse.getOwner(), onlyDisplayedOnCreate, datasetType); } CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); @@ -900,6 +901,29 @@ public List findAllInMetadataBlockAndDataverse(MetadataBlock m Root metadataBlockRoot = criteriaQuery.from(MetadataBlock.class); Root datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class); + + // Build the main predicate to include fields that belong to the specified dataverse and metadataBlock and match the onlyDisplayedOnCreate value. + Predicate fieldPresentInDataverse = buildFieldPresentInDataversePredicate(dataverse, onlyDisplayedOnCreate, criteriaQuery, criteriaBuilder, datasetFieldTypeRoot, metadataBlockRoot); + + // Build an additional predicate to include fields from the datasetType, if the datasetType is specified and contains the given metadataBlock. + Predicate fieldPresentInDatasetType = buildFieldPresentInDatasetTypePredicate(datasetType, criteriaQuery, criteriaBuilder, datasetFieldTypeRoot, metadataBlockRoot, onlyDisplayedOnCreate); + + // Build the final WHERE clause by combining all the predicates. + criteriaQuery.where( + criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID. + datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock. + criteriaBuilder.or( + fieldPresentInDataverse, + fieldPresentInDatasetType + ) + ); + + criteriaQuery.select(datasetFieldTypeRoot); + + return em.createQuery(criteriaQuery).getResultList(); + } + + private Predicate buildFieldPresentInDataversePredicate(Dataverse dataverse, boolean onlyDisplayedOnCreate, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder, Root datasetFieldTypeRoot, Root metadataBlockRoot) { Root dataverseRoot = criteriaQuery.from(Dataverse.class); // Join Dataverse with DataverseFieldTypeInputLevel on the "dataverseFieldTypeInputLevels" attribute, using a LEFT JOIN. @@ -930,7 +954,7 @@ public List findAllInMetadataBlockAndDataverse(MetadataBlock m Predicate hasNoInputLevelPredicate = criteriaBuilder.not(criteriaBuilder.exists(subquery)); // Define a predicate to include the required fields in Dataverse. - Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot); + Predicate fieldRequiredInTheInstallation = buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot); // Define a predicate for displaying DatasetFieldTypes on create. // If onlyDisplayedOnCreate is true, include fields that: @@ -941,28 +965,57 @@ public List findAllInMetadataBlockAndDataverse(MetadataBlock m ? criteriaBuilder.or( criteriaBuilder.or( criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")), - requiredInDataversePredicate + fieldRequiredInTheInstallation ), requiredAsInputLevelPredicate ) : criteriaBuilder.conjunction(); - // Build the final WHERE clause by combining all the predicates. - criteriaQuery.where( + // Combine all the predicates. + return criteriaBuilder.and( criteriaBuilder.equal(dataverseRoot.get("id"), dataverse.getId()), // Match the Dataverse ID. - criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID. metadataBlockRoot.in(dataverseRoot.get("metadataBlocks")), // Ensure the MetadataBlock is part of the Dataverse. - datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock. criteriaBuilder.or(includedAsInputLevelPredicate, hasNoInputLevelPredicate), // Include DatasetFieldTypes based on the input level predicates. displayedOnCreatePredicate // Apply the display-on-create filter if necessary. ); + } - criteriaQuery.select(datasetFieldTypeRoot).distinct(true); - - return em.createQuery(criteriaQuery).getResultList(); + private Predicate buildFieldPresentInDatasetTypePredicate(DatasetType datasetType, + CriteriaQuery criteriaQuery, + CriteriaBuilder criteriaBuilder, + Root datasetFieldTypeRoot, + Root metadataBlockRoot, + boolean onlyDisplayedOnCreate) { + Predicate datasetTypePredicate = criteriaBuilder.isFalse(criteriaBuilder.literal(true)); // Initialize datasetTypePredicate to always false by default + if (datasetType != null) { + // Create a subquery to check for the presence of the specified metadataBlock within the datasetType + Subquery datasetTypeSubquery = criteriaQuery.subquery(Long.class); + Root datasetTypeRoot = criteriaQuery.from(DatasetType.class); + + // Define a predicate for displaying DatasetFieldTypes on create. + // If onlyDisplayedOnCreate is true, include fields that are either marked as displayed on create OR marked as required. + // Otherwise, use an always-true predicate (conjunction). + Predicate displayedOnCreatePredicate = onlyDisplayedOnCreate ? + criteriaBuilder.or( + criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")), + buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot) + ) + : criteriaBuilder.conjunction(); + + datasetTypeSubquery.select(criteriaBuilder.literal(1L)) + .where( + criteriaBuilder.equal(datasetTypeRoot.get("id"), datasetType.getId()), // Match the DatasetType ID. + metadataBlockRoot.in(datasetTypeRoot.get("metadataBlocks")), // Ensure the metadataBlock is included in the datasetType's list of metadata blocks. + displayedOnCreatePredicate + ); + + // Now set the datasetTypePredicate to true if the subquery finds a matching metadataBlock + datasetTypePredicate = criteriaBuilder.exists(datasetTypeSubquery); + } + return datasetTypePredicate; } - private Predicate buildRequiredInDataversePredicate(CriteriaBuilder criteriaBuilder, Root datasetFieldTypeRoot) { + private Predicate buildFieldRequiredInTheInstallationPredicate(CriteriaBuilder criteriaBuilder, Root datasetFieldTypeRoot) { // Predicate to check if the current DatasetFieldType is required. Predicate isRequired = criteriaBuilder.isTrue(datasetFieldTypeRoot.get("required")); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java b/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java index 907295ad848..cbb0f4ffcfd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java @@ -42,6 +42,7 @@ import java.util.logging.Logger; import jakarta.persistence.NoResultException; import jakarta.persistence.TypedQuery; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response.Status; import java.io.BufferedInputStream; @@ -545,4 +546,19 @@ public static String getDataverseLangDirectory() { return dataverseLangDirectory; } + /** + * Set setDisplayOnCreate for a DatasetFieldType. + */ + @POST + @Path("/setDisplayOnCreate") + public Response setDisplayOnCreate(@QueryParam("datasetFieldType") String datasetFieldTypeIn, @QueryParam("setDisplayOnCreate") boolean setDisplayOnCreateIn) { + DatasetFieldType dft = datasetFieldService.findByName(datasetFieldTypeIn); + if (dft == null) { + return error(Status.NOT_FOUND, "Cound not find a DatasetFieldType by looking up " + datasetFieldTypeIn); + } + dft.setDisplayOnCreate(setDisplayOnCreateIn); + DatasetFieldType saved = datasetFieldService.save(dft); + return ok("DisplayOnCreate for DatasetFieldType " + saved.getName() + " is now " + saved.isDisplayOnCreate()); + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 5ccec629ee4..1a7c6dc5de3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -5202,14 +5202,10 @@ public Response resetPidGenerator(@Context ContainerRequestContext crc, @PathPar @Path("datasetTypes") public Response getDatasetTypes() { JsonArrayBuilder jab = Json.createArrayBuilder(); - List datasetTypes = datasetTypeSvc.listAll(); - for (DatasetType datasetType : datasetTypes) { - JsonObjectBuilder job = Json.createObjectBuilder(); - job.add("id", datasetType.getId()); - job.add("name", datasetType.getName()); - jab.add(job); + for (DatasetType datasetType : datasetTypeSvc.listAll()) { + jab.add(datasetType.toJson()); } - return ok(jab.build()); + return ok(jab); } @GET @@ -5324,4 +5320,52 @@ public Response deleteDatasetType(@Context ContainerRequestContext crc, @PathPar } } + @AuthRequired + @PUT + @Path("datasetTypes/{idOrName}") + public Response updateDatasetTypeLinksWithMetadataBlocks(@Context ContainerRequestContext crc, @PathParam("idOrName") String idOrName, String jsonBody) { + DatasetType datasetType = null; + if (StringUtils.isNumeric(idOrName)) { + try { + long id = Long.parseLong(idOrName); + datasetType = datasetTypeSvc.getById(id); + } catch (NumberFormatException ex) { + return error(NOT_FOUND, "Could not find a dataset type with id " + idOrName); + } + } else { + datasetType = datasetTypeSvc.getByName(idOrName); + } + JsonArrayBuilder datasetTypesBefore = Json.createArrayBuilder(); + for (MetadataBlock metadataBlock : datasetType.getMetadataBlocks()) { + datasetTypesBefore.add(metadataBlock.getName()); + } + JsonArrayBuilder datasetTypesAfter = Json.createArrayBuilder(); + List metadataBlocksToSave = new ArrayList<>(); + if (jsonBody != null && !jsonBody.isEmpty()) { + JsonArray json = JsonUtil.getJsonArray(jsonBody); + for (JsonString jsonValue : json.getValuesAs(JsonString.class)) { + String name = jsonValue.getString(); + MetadataBlock metadataBlock = metadataBlockSvc.findByName(name); + if (metadataBlock != null) { + metadataBlocksToSave.add(metadataBlock); + datasetTypesAfter.add(name); + } else { + String availableBlocks = metadataBlockSvc.listMetadataBlocks().stream().map(MetadataBlock::getName).collect(Collectors.joining(", ")); + return badRequest("Metadata block not found: " + name + ". Available metadata blocks: " + availableBlocks); + } + } + } + try { + execCommand(new UpdateDatasetTypeLinksToMetadataBlocksCommand(createDataverseRequest(getRequestUser(crc)), datasetType, metadataBlocksToSave)); + return ok(Json.createObjectBuilder() + .add("linkedMetadataBlocks", Json.createObjectBuilder() + .add("before", datasetTypesBefore) + .add("after", datasetTypesAfter)) + ); + + } catch (WrappedResponse ex) { + return ex.getResponse(); + } + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index bbd9476b9e8..81f84ec2fbf 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -17,6 +17,7 @@ import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; +import edu.harvard.iq.dataverse.dataset.DatasetType; import edu.harvard.iq.dataverse.dataverse.DataverseUtil; import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItem; import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItemServiceBean; @@ -848,17 +849,20 @@ public Response deleteDataverseLinkingDataverse(@Context ContainerRequestContext public Response listMetadataBlocks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("onlyDisplayedOnCreate") boolean onlyDisplayedOnCreate, - @QueryParam("returnDatasetFieldTypes") boolean returnDatasetFieldTypes) { + @QueryParam("returnDatasetFieldTypes") boolean returnDatasetFieldTypes, + @QueryParam("datasetType") String datasetTypeIn) { try { Dataverse dataverse = findDataverseOrDie(dvIdtf); + DatasetType datasetType = datasetTypeSvc.getByName(datasetTypeIn); final List metadataBlocks = execCommand( new ListMetadataBlocksCommand( createDataverseRequest(getRequestUser(crc)), dataverse, - onlyDisplayedOnCreate + onlyDisplayedOnCreate, + datasetType ) ); - return ok(json(metadataBlocks, returnDatasetFieldTypes, onlyDisplayedOnCreate, dataverse)); + return ok(json(metadataBlocks, returnDatasetFieldTypes, onlyDisplayedOnCreate, dataverse, datasetType)); } catch (WrappedResponse we) { return we.getResponse(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java b/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java index 78bf232e1a6..727703852eb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java +++ b/src/main/java/edu/harvard/iq/dataverse/dataset/DatasetType.java @@ -1,17 +1,23 @@ package edu.harvard.iq.dataverse.dataset; +import edu.harvard.iq.dataverse.MetadataBlock; import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObjectBuilder; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; @NamedQueries({ @NamedQuery(name = "DatasetType.findAll", @@ -42,6 +48,12 @@ public class DatasetType implements Serializable { @Column(nullable = false) private String name; + /** + * The metadata blocks this dataset type is linked to. + */ + @ManyToMany(cascade = {CascadeType.MERGE}) + private List metadataBlocks = new ArrayList<>(); + public DatasetType() { } @@ -61,10 +73,23 @@ public void setName(String name) { this.name = name; } + public List getMetadataBlocks() { + return metadataBlocks; + } + + public void setMetadataBlocks(List metadataBlocks) { + this.metadataBlocks = metadataBlocks; + } + public JsonObjectBuilder toJson() { + JsonArrayBuilder linkedMetadataBlocks = Json.createArrayBuilder(); + for (MetadataBlock metadataBlock : this.getMetadataBlocks()) { + linkedMetadataBlocks.add(metadataBlock.getName()); + } return Json.createObjectBuilder() .add("id", getId()) - .add("name", getName()); + .add("name", getName()) + .add("linkedMetadataBlocks", linkedMetadataBlocks); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListMetadataBlocksCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListMetadataBlocksCommand.java index 8275533ced2..e79d36de07d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListMetadataBlocksCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ListMetadataBlocksCommand.java @@ -3,15 +3,18 @@ import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.MetadataBlock; import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.dataset.DatasetType; import edu.harvard.iq.dataverse.engine.command.AbstractCommand; import edu.harvard.iq.dataverse.engine.command.CommandContext; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; /** * Lists the metadata blocks of a {@link Dataverse}. @@ -23,11 +26,13 @@ public class ListMetadataBlocksCommand extends AbstractCommand execute(CommandContext ctxt) throws CommandException if (onlyDisplayedOnCreate) { return listMetadataBlocksDisplayedOnCreate(ctxt, dataverse); } - return dataverse.getMetadataBlocks(); + List orig = dataverse.getMetadataBlocks(); + List extraFromDatasetTypes = new ArrayList<>(); + if (datasetType != null) { + extraFromDatasetTypes = datasetType.getMetadataBlocks(); + } + return Stream.concat(orig.stream(), extraFromDatasetTypes.stream()).toList(); } private List listMetadataBlocksDisplayedOnCreate(CommandContext ctxt, Dataverse dataverse) { if (dataverse.isMetadataBlockRoot() || dataverse.getOwner() == null) { - return ctxt.metadataBlocks().listMetadataBlocksDisplayedOnCreate(dataverse); + List orig = ctxt.metadataBlocks().listMetadataBlocksDisplayedOnCreate(dataverse); + List extraFromDatasetTypes = new ArrayList<>(); + if (datasetType != null) { + extraFromDatasetTypes = datasetType.getMetadataBlocks(); + } + return Stream.concat(orig.stream(), extraFromDatasetTypes.stream()).toList(); } return listMetadataBlocksDisplayedOnCreate(ctxt, dataverse.getOwner()); } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeLinksToMetadataBlocksCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeLinksToMetadataBlocksCommand.java new file mode 100644 index 00000000000..57b6da3f90c --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/UpdateDatasetTypeLinksToMetadataBlocksCommand.java @@ -0,0 +1,37 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.MetadataBlock; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.dataset.DatasetType; +import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.PermissionException; +import java.util.List; + +@RequiredPermissions({}) +public class UpdateDatasetTypeLinksToMetadataBlocksCommand extends AbstractVoidCommand { + + final DatasetType datasetType; + List metadataBlocks; + + public UpdateDatasetTypeLinksToMetadataBlocksCommand(DataverseRequest dataverseRequest, DatasetType datasetType, List metadataBlocks) { + super(dataverseRequest, (DvObject) null); + this.datasetType = datasetType; + this.metadataBlocks = metadataBlocks; + } + + @Override + protected void executeImpl(CommandContext ctxt) throws CommandException { + if (!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser()) { + throw new PermissionException("Update dataset type links to metadata block command can only be called by superusers.", + this, null, null); + } + datasetType.setMetadataBlocks(metadataBlocks); + ctxt.em().merge(datasetType); + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index b88dfaef4b5..646bdadf701 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -17,6 +17,7 @@ import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.dataaccess.DataAccess; +import edu.harvard.iq.dataverse.dataset.DatasetType; import edu.harvard.iq.dataverse.dataset.DatasetUtil; import edu.harvard.iq.dataverse.datavariable.CategoryMetadata; import edu.harvard.iq.dataverse.datavariable.DataVariable; @@ -607,13 +608,13 @@ public static JsonObjectBuilder json(MetadataBlock block, List fie } public static JsonArrayBuilder json(List metadataBlocks, boolean returnDatasetFieldTypes, boolean printOnlyDisplayedOnCreateDatasetFieldTypes) { - return json(metadataBlocks, returnDatasetFieldTypes, printOnlyDisplayedOnCreateDatasetFieldTypes, null); + return json(metadataBlocks, returnDatasetFieldTypes, printOnlyDisplayedOnCreateDatasetFieldTypes, null, null); } - public static JsonArrayBuilder json(List metadataBlocks, boolean returnDatasetFieldTypes, boolean printOnlyDisplayedOnCreateDatasetFieldTypes, Dataverse ownerDataverse) { + public static JsonArrayBuilder json(List metadataBlocks, boolean returnDatasetFieldTypes, boolean printOnlyDisplayedOnCreateDatasetFieldTypes, Dataverse ownerDataverse, DatasetType datasetType) { JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); for (MetadataBlock metadataBlock : metadataBlocks) { - arrayBuilder.add(returnDatasetFieldTypes ? json(metadataBlock, printOnlyDisplayedOnCreateDatasetFieldTypes, ownerDataverse) : brief.json(metadataBlock)); + arrayBuilder.add(returnDatasetFieldTypes ? json(metadataBlock, printOnlyDisplayedOnCreateDatasetFieldTypes, ownerDataverse, datasetType) : brief.json(metadataBlock)); } return arrayBuilder; } @@ -641,10 +642,10 @@ public static JsonObject json(DatasetField dfv) { } public static JsonObjectBuilder json(MetadataBlock metadataBlock) { - return json(metadataBlock, false, null); + return json(metadataBlock, false, null, null); } - public static JsonObjectBuilder json(MetadataBlock metadataBlock, boolean printOnlyDisplayedOnCreateDatasetFieldTypes, Dataverse ownerDataverse) { + public static JsonObjectBuilder json(MetadataBlock metadataBlock, boolean printOnlyDisplayedOnCreateDatasetFieldTypes, Dataverse ownerDataverse, DatasetType datasetType) { JsonObjectBuilder jsonObjectBuilder = jsonObjectBuilder() .add("id", metadataBlock.getId()) .add("name", metadataBlock.getName()) @@ -655,7 +656,7 @@ public static JsonObjectBuilder json(MetadataBlock metadataBlock, boolean printO if (ownerDataverse != null) { datasetFieldTypesList = datasetFieldService.findAllInMetadataBlockAndDataverse( - metadataBlock, ownerDataverse, printOnlyDisplayedOnCreateDatasetFieldTypes); + metadataBlock, ownerDataverse, printOnlyDisplayedOnCreateDatasetFieldTypes, datasetType); } else { datasetFieldTypesList = printOnlyDisplayedOnCreateDatasetFieldTypes ? datasetFieldService.findAllDisplayedOnCreateInMetadataBlock(metadataBlock) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java index a0b9f5325d0..7c73498dead 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java @@ -12,34 +12,40 @@ import java.util.UUID; import org.hamcrest.CoreMatchers; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class DatasetTypesIT { + final static String INSTRUMENT = "instrument"; + @BeforeAll public static void setUpClass() { RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); - Response getSoftwareType = UtilIT.getDatasetType(DatasetType.DATASET_TYPE_SOFTWARE); - getSoftwareType.prettyPrint(); - - String typeFound = JsonPath.from(getSoftwareType.getBody().asString()).getString("data.name"); - System.out.println("type found: " + typeFound); - if (DatasetType.DATASET_TYPE_SOFTWARE.equals(typeFound)) { - return; - } - - System.out.println("The \"software\" type wasn't found. Create it."); Response createUser = UtilIT.createRandomUser(); createUser.then().assertThat().statusCode(OK.getStatusCode()); String username = UtilIT.getUsernameFromResponse(createUser); String apiToken = UtilIT.getApiTokenFromResponse(createUser); UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode()); - String jsonIn = Json.createObjectBuilder().add("name", DatasetType.DATASET_TYPE_SOFTWARE).build().toString(); + ensureDatasetTypeIsPresent(DatasetType.DATASET_TYPE_SOFTWARE, apiToken); + ensureDatasetTypeIsPresent(INSTRUMENT, apiToken); + } + private static void ensureDatasetTypeIsPresent(String datasetType, String apiToken) { + Response getDatasetType = UtilIT.getDatasetType(datasetType); + getDatasetType.prettyPrint(); + String typeFound = JsonPath.from(getDatasetType.getBody().asString()).getString("data.name"); + System.out.println("type found: " + typeFound); + if (datasetType.equals(typeFound)) { + return; + } + System.out.println("The " + datasetType + "type wasn't found. Create it."); + String jsonIn = Json.createObjectBuilder().add("name", datasetType).build().toString(); Response typeAdded = UtilIT.addDatasetType(jsonIn, apiToken); typeAdded.prettyPrint(); typeAdded.then().assertThat().statusCode(OK.getStatusCode()); @@ -265,4 +271,224 @@ public void testAddAndDeleteDatasetType() { } + @Test + public void testUpdateDatasetTypeLinksWithMetadataBlocks() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode()); + + System.out.println("listing root collection blocks with display on create: only citation"); + Response listBlocks = UtilIT.listMetadataBlocks(":root", true, false, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", nullValue()); + + System.out.println("listing root collection blocks without display on create: only citation"); + listBlocks = UtilIT.listMetadataBlocks(":root", false, false, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", nullValue()); + + //Avoid all-numeric names (which are not allowed) + String randomName = "zzz" + UUID.randomUUID().toString().substring(0, 8); + String jsonIn = Json.createObjectBuilder().add("name", randomName).build().toString(); + + System.out.println("adding type with name " + randomName); + Response typeAdded = UtilIT.addDatasetType(jsonIn, apiToken); + typeAdded.prettyPrint(); + typeAdded.then().assertThat().statusCode(OK.getStatusCode()); + + Long typeId = JsonPath.from(typeAdded.getBody().asString()).getLong("data.id"); + + System.out.println("id of type: " + typeId); + Response getTypeById = UtilIT.getDatasetType(typeId.toString()); + getTypeById.prettyPrint(); + getTypeById.then().assertThat().statusCode(OK.getStatusCode()); + + String metadataBlockToLink = """ + ["geospatial"] +"""; + + Response linkDatasetType1ToGeospatial = UtilIT.updateDatasetTypeLinksWithMetadataBlocks(randomName, metadataBlockToLink, apiToken); + linkDatasetType1ToGeospatial.prettyPrint(); + linkDatasetType1ToGeospatial.then().assertThat(). + statusCode(OK.getStatusCode()) + .body("data.linkedMetadataBlocks.after[0]", CoreMatchers.is("geospatial")); + + getTypeById = UtilIT.getDatasetType(typeId.toString()); + getTypeById.prettyPrint(); + getTypeById.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.linkedMetadataBlocks[0]", CoreMatchers.is("geospatial")); + + System.out.println("listing root collection blocks with display on create"); + listBlocks = UtilIT.listMetadataBlocks(":root", true, false, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[2].name", nullValue()); + + System.out.println("listing root collection blocks without display on create"); + listBlocks = UtilIT.listMetadataBlocks(":root", false, false, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[2].name", nullValue()); + + Response createDataverse = UtilIT.createRandomDataverse(apiToken); + createDataverse.then().assertThat().statusCode(CREATED.getStatusCode()); + + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse); + Integer dataverseId = UtilIT.getDataverseIdFromResponse(createDataverse); + + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode()); + + System.out.println("listing " + dataverseAlias + " collection blocks with display on create using dataset type " + randomName); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, true, false, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[2].name", nullValue()); + + System.out.println("listing " + dataverseAlias + " collection blocks without display on create using dataset type " + randomName); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, false, false, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[2].name", nullValue()); + + System.out.println("listing " + dataverseAlias + " collection blocks and inner dataset field types, without display on create and return dataset field types set to true using dataset type " + randomName); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, false, true, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[0].fields.size()", is(35)) + .body("data[1].fields.size()", is(3)); + + System.out.println("listing " + dataverseAlias + " collection blocks and inner dataset field types, with display on create and return dataset field types set to true using dataset type " + randomName); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, true, true, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("geospatial")) + .body("data[0].fields.size()", is(10)) + .body("data[1].fields.size()", is(0)); // There are no fields required or with displayOnCreate=true in geospatial.tsv + + // We send an empty array to mean "delete or clear all" + String emptyJsonArray = "[]"; + Response removeDatasetTypeLinks = UtilIT.updateDatasetTypeLinksWithMetadataBlocks(randomName, emptyJsonArray, apiToken); + removeDatasetTypeLinks.prettyPrint(); + removeDatasetTypeLinks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.linkedMetadataBlocks.after[0]", CoreMatchers.nullValue()); + + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, true, false, randomName, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .body("data[0].name", is("citation")); + } + + @Test + public void testLinkInstrumentToAstro() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().assertThat().statusCode(OK.getStatusCode()); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + UtilIT.setSuperuserStatus(username, true).then().assertThat().statusCode(OK.getStatusCode()); + + String metadataBlockLink = """ + ["astrophysics"] +//"""; + + String datasetType = "instrument"; + Response linkInstrumentToAstro = UtilIT.updateDatasetTypeLinksWithMetadataBlocks(datasetType, metadataBlockLink, apiToken); + linkInstrumentToAstro.prettyPrint(); + linkInstrumentToAstro.then().assertThat(). + statusCode(OK.getStatusCode()) + .body("data.linkedMetadataBlocks.after[0]", CoreMatchers.is("astrophysics")); + + Response createDataverse = UtilIT.createRandomDataverse(apiToken); + createDataverse.then().assertThat().statusCode(CREATED.getStatusCode()); + + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse); + Integer dataverseId = UtilIT.getDataverseIdFromResponse(createDataverse); + + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode()); + + // displayOnCreate will only be true for fields that are set this way in the database. + // We set it here so we can make assertions below. + UtilIT.setDisplayOnCreate("astroInstrument", true); + + Response listBlocks = null; + System.out.println("listing root collection blocks with display on create using dataset type " + datasetType); + listBlocks = UtilIT.listMetadataBlocks(":root", true, true, datasetType, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("astrophysics")) + .body("data[2].name", nullValue()) + .body("data[0].fields.title.displayOnCreate", equalTo(true)) + .body("data[1].fields.astroInstrument.displayOnCreate", equalTo(true)); + + System.out.println("listing root collection blocks with all fields (not display on create) using dataset type " + datasetType); + listBlocks = UtilIT.listMetadataBlocks(":root", false, true, datasetType, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("astrophysics")) + .body("data[2].name", nullValue()) + .body("data[0].fields.title.displayOnCreate", equalTo(true)) + .body("data[0].fields.subtitle.displayOnCreate", equalTo(false)) + .body("data[1].fields.astroInstrument.displayOnCreate", equalTo(true)) + .body("data[1].fields.astroObject.displayOnCreate", equalTo(false)); + + System.out.println("listing " + dataverseAlias + " collection blocks with display on create using dataset type " + datasetType); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, true, true, datasetType, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("astrophysics")) + .body("data[2].name", nullValue()) + .body("data[0].fields.title.displayOnCreate", equalTo(true)) + // subtitle is hidden because it is not "display on create" + .body("data[0].fields.subtitle", nullValue()) + .body("data[1].fields.astroInstrument.displayOnCreate", equalTo(true)) + // astroObject is hidden because it is not "display on create" + .body("data[1].fields.astroObject", nullValue()); + + System.out.println("listing " + dataverseAlias + " collection blocks with all fields (not display on create) using dataset type " + datasetType); + listBlocks = UtilIT.listMetadataBlocks(dataverseAlias, false, true, datasetType, apiToken); + listBlocks.prettyPrint(); + listBlocks.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].name", is("citation")) + .body("data[1].name", is("astrophysics")) + .body("data[2].name", nullValue()) + .body("data[0].fields.title.displayOnCreate", equalTo(true)) + .body("data[0].fields.subtitle.displayOnCreate", equalTo(false)) + .body("data[1].fields.astroInstrument.displayOnCreate", equalTo(true)) + .body("data[1].fields.astroObject.displayOnCreate", equalTo(false)); + + } + } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index b04f19381b4..50bda95e5a8 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -806,11 +806,18 @@ static Response setMetadataBlocks(String dataverseAlias, JsonArrayBuilder blocks } static Response listMetadataBlocks(String dataverseAlias, boolean onlyDisplayedOnCreate, boolean returnDatasetFieldTypes, String apiToken) { - return given() + return listMetadataBlocks(dataverseAlias, onlyDisplayedOnCreate, returnDatasetFieldTypes, null, apiToken); + } + + static Response listMetadataBlocks(String dataverseAlias, boolean onlyDisplayedOnCreate, boolean returnDatasetFieldTypes, String datasetType, String apiToken) { + RequestSpecification requestSpecification = given() .header(API_TOKEN_HTTP_HEADER, apiToken) .queryParam("onlyDisplayedOnCreate", onlyDisplayedOnCreate) - .queryParam("returnDatasetFieldTypes", returnDatasetFieldTypes) - .get("/api/dataverses/" + dataverseAlias + "/metadatablocks"); + .queryParam("returnDatasetFieldTypes", returnDatasetFieldTypes); + if (datasetType != null) { + requestSpecification.queryParam("datasetType", datasetType); + } + return requestSpecification.get("/api/dataverses/" + dataverseAlias + "/metadatablocks"); } static Response listMetadataBlocks(boolean onlyDisplayedOnCreate, boolean returnDatasetFieldTypes) { @@ -825,6 +832,13 @@ static Response getMetadataBlock(String block) { .get("/api/metadatablocks/" + block); } + static Response setDisplayOnCreate(String datasetFieldType, boolean setDisplayOnCreate) { + return given() + .queryParam("datasetFieldType", datasetFieldType) + .queryParam("setDisplayOnCreate", setDisplayOnCreate) + .post("/api/admin/datasetfield/setDisplayOnCreate"); + } + static private String getDatasetXml(String title, String author, String description) { String nullLicense = null; String nullRights = null; @@ -4374,7 +4388,6 @@ static Response getDatasetType(String idOrName) { } static Response addDatasetType(String jsonIn, String apiToken) { - System.out.println("called addDatasetType..."); return given() .header(API_TOKEN_HTTP_HEADER, apiToken) .body(jsonIn) @@ -4388,6 +4401,13 @@ static Response deleteDatasetTypes(long doomed, String apiToken) { .delete("/api/datasets/datasetTypes/" + doomed); } + static Response updateDatasetTypeLinksWithMetadataBlocks(String idOrName, String jsonArrayOfMetadataBlocks, String apiToken) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .body(jsonArrayOfMetadataBlocks) + .put("/api/datasets/datasetTypes/" + idOrName); + } + static Response registerOidcUser(String jsonIn, String bearerToken) { return given() .header(HttpHeaders.AUTHORIZATION, bearerToken)