Skip to content

Commit 9ee8754

Browse files
authored
Merge pull request IQSS#11001 from IQSS/10519-dataset-types
allow links between dataset types and metadata blocks
2 parents aebec05 + ce03b30 commit 9ee8754

File tree

14 files changed

+569
-53
lines changed

14 files changed

+569
-53
lines changed

Diff for: doc/release-notes/10519-dataset-types.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Dataset Types can be linked to Metadata Blocks
2+
3+
Metadata blocks (e.g. "CodeMeta") can now be linked to dataset types (e.g. "software") using new superuser APIs.
4+
5+
This will have the following effects for the APIs used by the new Dataverse UI ( https://github.com/IQSS/dataverse-frontend ):
6+
7+
- 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.
8+
- 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.
9+
10+
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.
11+
12+
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 for: doc/sphinx-guides/source/api/native-api.rst

+61-2
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ The fully expanded example above (without environment variables) looks like this
540540
541541
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/dataverses/root/assignments/6"
542542
543+
.. _list-metadata-blocks-for-a-collection:
544+
543545
List Metadata Blocks Defined on a Dataverse Collection
544546
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
545547

@@ -567,6 +569,7 @@ This endpoint supports the following optional query parameters:
567569

568570
- ``returnDatasetFieldTypes``: Whether or not to return the dataset field types present in each metadata block. If not set, the default value is false.
569571
- ``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.
572+
- ``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`.
570573

571574
An example using the optional query parameters is presented below:
572575

@@ -575,14 +578,17 @@ An example using the optional query parameters is presented below:
575578
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
576579
export SERVER_URL=https://demo.dataverse.org
577580
export ID=root
581+
export DATASET_TYPE=software
578582
579-
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/dataverses/$ID/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true"
583+
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/dataverses/$ID/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true&datasetType=$DATASET_TYPE"
580584
581585
The fully expanded example above (without environment variables) looks like this:
582586

583587
.. code-block:: bash
584588
585-
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true"
589+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/metadatablocks?returnDatasetFieldTypes=true&onlyDisplayedOnCreate=true&datasetType=software"
590+
591+
.. _define-metadata-blocks-for-a-dataverse-collection:
586592

587593
Define Metadata Blocks for a Dataverse Collection
588594
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -609,6 +615,8 @@ The fully expanded example above (without environment variables) looks like this
609615
610616
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"
611617
618+
An alternative to defining metadata blocks at a collection level is to create and use a dataset type. See :ref:`api-link-dataset-type`.
619+
612620
Determine if a Dataverse Collection Inherits Its Metadata Blocks from Its Parent
613621
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
614622

@@ -3473,6 +3481,36 @@ The fully expanded example above (without environment variables) looks like this
34733481
34743482
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/datasets/datasetTypes/3"
34753483
3484+
.. _api-link-dataset-type:
3485+
3486+
Link Dataset Type with Metadata Blocks
3487+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3488+
3489+
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`).
3490+
3491+
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.
3492+
3493+
This API endpoint is for superusers only.
3494+
3495+
.. code-block:: bash
3496+
3497+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3498+
export SERVER_URL=https://demo.dataverse.org
3499+
export TYPE=software
3500+
export JSON='["codeMeta20"]'
3501+
3502+
curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-Type: application/json" "$SERVER_URL/api/datasets/datasetTypes/$TYPE" -X PUT -d $JSON
3503+
3504+
The fully expanded example above (without environment variables) looks like this:
3505+
3506+
.. code-block:: bash
3507+
3508+
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"]'
3509+
3510+
To update the blocks that are linked, send an array with those blocks.
3511+
3512+
To remove all links to blocks, send an empty array.
3513+
34763514
Files
34773515
-----
34783516
@@ -5256,6 +5294,27 @@ The fully expanded example above (without environment variables) looks like this
52565294
52575295
curl "https://demo.dataverse.org/api/datasetfields/facetables"
52585296
5297+
.. _setDisplayOnCreate:
5298+
5299+
Set displayOnCreate for a Dataset Field
5300+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5301+
5302+
Set displayOnCreate for a dataset field. See also :doc:`/admin/metadatacustomization` in the Admin Guide.
5303+
5304+
.. code-block:: bash
5305+
5306+
export SERVER_URL=http://localhost:8080
5307+
export FIELD=subtitle
5308+
export BOOLEAN=true
5309+
5310+
curl -X POST "$SERVER_URL/api/admin/datasetfield/setDisplayOnCreate?datasetFieldType=$FIELD&setDisplayOnCreate=$BOOLEAN"
5311+
5312+
The fully expanded example above (without environment variables) looks like this:
5313+
5314+
.. code-block:: bash
5315+
5316+
curl -X POST "http://localhost:8080/api/admin/datasetfield/setDisplayOnCreate?datasetFieldType=studyAssayCellType&setDisplayOnCreate=true"
5317+
52595318
.. _Notifications:
52605319
52615320
Notifications

Diff for: doc/sphinx-guides/source/user/dataset-management.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -801,21 +801,23 @@ If you deaccession the most recently published version of the dataset but not al
801801
Dataset Types
802802
=============
803803

804+
.. note:: Development of the dataset types feature is ongoing. Please see https://github.com/IQSS/dataverse-pm/issues/307 for details.
805+
804806
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.
805807

806808
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.
807809

808810
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.
809811

810-
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:
812+
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:
811813

812814
- :ref:`api-create-dataset-with-type` (Native API)
813815
- :ref:`api-semantic-create-dataset-with-type` (Semantic API)
814816
- :ref:`import-dataset-with-type`
815817

816818
Dataset types can be listed, added, or deleted via API. See :ref:`api-dataset-types` in the API Guide for more.
817819

818-
Development of the dataset types feature is ongoing. Please see https://github.com/IQSS/dataverse/issues/10489 for details.
820+
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.
819821

820822
.. |image1| image:: ./img/DatasetDiagram.png
821823
:class: img-responsive

Diff for: docker-compose-dev.yml

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ services:
9090
- dev
9191
networks:
9292
- dataverse
93+
volumes:
94+
- ./docker-dev-volumes/solr/data:/var/solr
9395

9496
dev_dv_initializer:
9597
container_name: "dev_dv_initializer"

Diff for: src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java

+67-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package edu.harvard.iq.dataverse;
22

3+
import edu.harvard.iq.dataverse.dataset.DatasetType;
34
import java.io.IOException;
45
import java.io.StringReader;
56
import java.net.URI;
@@ -871,15 +872,15 @@ public List<DatasetFieldType> findAllDisplayedOnCreateInMetadataBlock(MetadataBl
871872
Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
872873
Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
873874

874-
Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
875+
Predicate fieldRequiredInTheInstallation = buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot);
875876

876877
criteriaQuery.where(
877878
criteriaBuilder.and(
878879
criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()),
879880
datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")),
880881
criteriaBuilder.or(
881882
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
882-
requiredInDataversePredicate
883+
fieldRequiredInTheInstallation
883884
)
884885
)
885886
);
@@ -890,16 +891,39 @@ public List<DatasetFieldType> findAllDisplayedOnCreateInMetadataBlock(MetadataBl
890891
return typedQuery.getResultList();
891892
}
892893

893-
public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate) {
894+
public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate, DatasetType datasetType) {
894895
if (!dataverse.isMetadataBlockRoot() && dataverse.getOwner() != null) {
895-
return findAllInMetadataBlockAndDataverse(metadataBlock, dataverse.getOwner(), onlyDisplayedOnCreate);
896+
return findAllInMetadataBlockAndDataverse(metadataBlock, dataverse.getOwner(), onlyDisplayedOnCreate, datasetType);
896897
}
897898

898899
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
899900
CriteriaQuery<DatasetFieldType> criteriaQuery = criteriaBuilder.createQuery(DatasetFieldType.class);
900901

901902
Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
902903
Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
904+
905+
// Build the main predicate to include fields that belong to the specified dataverse and metadataBlock and match the onlyDisplayedOnCreate value.
906+
Predicate fieldPresentInDataverse = buildFieldPresentInDataversePredicate(dataverse, onlyDisplayedOnCreate, criteriaQuery, criteriaBuilder, datasetFieldTypeRoot, metadataBlockRoot);
907+
908+
// Build an additional predicate to include fields from the datasetType, if the datasetType is specified and contains the given metadataBlock.
909+
Predicate fieldPresentInDatasetType = buildFieldPresentInDatasetTypePredicate(datasetType, criteriaQuery, criteriaBuilder, datasetFieldTypeRoot, metadataBlockRoot, onlyDisplayedOnCreate);
910+
911+
// Build the final WHERE clause by combining all the predicates.
912+
criteriaQuery.where(
913+
criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID.
914+
datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock.
915+
criteriaBuilder.or(
916+
fieldPresentInDataverse,
917+
fieldPresentInDatasetType
918+
)
919+
);
920+
921+
criteriaQuery.select(datasetFieldTypeRoot);
922+
923+
return em.createQuery(criteriaQuery).getResultList();
924+
}
925+
926+
private Predicate buildFieldPresentInDataversePredicate(Dataverse dataverse, boolean onlyDisplayedOnCreate, CriteriaQuery<DatasetFieldType> criteriaQuery, CriteriaBuilder criteriaBuilder, Root<DatasetFieldType> datasetFieldTypeRoot, Root<MetadataBlock> metadataBlockRoot) {
903927
Root<Dataverse> dataverseRoot = criteriaQuery.from(Dataverse.class);
904928

905929
// Join Dataverse with DataverseFieldTypeInputLevel on the "dataverseFieldTypeInputLevels" attribute, using a LEFT JOIN.
@@ -930,7 +954,7 @@ public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock m
930954
Predicate hasNoInputLevelPredicate = criteriaBuilder.not(criteriaBuilder.exists(subquery));
931955

932956
// Define a predicate to include the required fields in Dataverse.
933-
Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
957+
Predicate fieldRequiredInTheInstallation = buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot);
934958

935959
// Define a predicate for displaying DatasetFieldTypes on create.
936960
// If onlyDisplayedOnCreate is true, include fields that:
@@ -941,28 +965,57 @@ public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock m
941965
? criteriaBuilder.or(
942966
criteriaBuilder.or(
943967
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
944-
requiredInDataversePredicate
968+
fieldRequiredInTheInstallation
945969
),
946970
requiredAsInputLevelPredicate
947971
)
948972
: criteriaBuilder.conjunction();
949973

950-
// Build the final WHERE clause by combining all the predicates.
951-
criteriaQuery.where(
974+
// Combine all the predicates.
975+
return criteriaBuilder.and(
952976
criteriaBuilder.equal(dataverseRoot.get("id"), dataverse.getId()), // Match the Dataverse ID.
953-
criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID.
954977
metadataBlockRoot.in(dataverseRoot.get("metadataBlocks")), // Ensure the MetadataBlock is part of the Dataverse.
955-
datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock.
956978
criteriaBuilder.or(includedAsInputLevelPredicate, hasNoInputLevelPredicate), // Include DatasetFieldTypes based on the input level predicates.
957979
displayedOnCreatePredicate // Apply the display-on-create filter if necessary.
958980
);
981+
}
959982

960-
criteriaQuery.select(datasetFieldTypeRoot).distinct(true);
961-
962-
return em.createQuery(criteriaQuery).getResultList();
983+
private Predicate buildFieldPresentInDatasetTypePredicate(DatasetType datasetType,
984+
CriteriaQuery<DatasetFieldType> criteriaQuery,
985+
CriteriaBuilder criteriaBuilder,
986+
Root<DatasetFieldType> datasetFieldTypeRoot,
987+
Root<MetadataBlock> metadataBlockRoot,
988+
boolean onlyDisplayedOnCreate) {
989+
Predicate datasetTypePredicate = criteriaBuilder.isFalse(criteriaBuilder.literal(true)); // Initialize datasetTypePredicate to always false by default
990+
if (datasetType != null) {
991+
// Create a subquery to check for the presence of the specified metadataBlock within the datasetType
992+
Subquery<Long> datasetTypeSubquery = criteriaQuery.subquery(Long.class);
993+
Root<DatasetType> datasetTypeRoot = criteriaQuery.from(DatasetType.class);
994+
995+
// Define a predicate for displaying DatasetFieldTypes on create.
996+
// If onlyDisplayedOnCreate is true, include fields that are either marked as displayed on create OR marked as required.
997+
// Otherwise, use an always-true predicate (conjunction).
998+
Predicate displayedOnCreatePredicate = onlyDisplayedOnCreate ?
999+
criteriaBuilder.or(
1000+
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
1001+
buildFieldRequiredInTheInstallationPredicate(criteriaBuilder, datasetFieldTypeRoot)
1002+
)
1003+
: criteriaBuilder.conjunction();
1004+
1005+
datasetTypeSubquery.select(criteriaBuilder.literal(1L))
1006+
.where(
1007+
criteriaBuilder.equal(datasetTypeRoot.get("id"), datasetType.getId()), // Match the DatasetType ID.
1008+
metadataBlockRoot.in(datasetTypeRoot.get("metadataBlocks")), // Ensure the metadataBlock is included in the datasetType's list of metadata blocks.
1009+
displayedOnCreatePredicate
1010+
);
1011+
1012+
// Now set the datasetTypePredicate to true if the subquery finds a matching metadataBlock
1013+
datasetTypePredicate = criteriaBuilder.exists(datasetTypeSubquery);
1014+
}
1015+
return datasetTypePredicate;
9631016
}
9641017

965-
private Predicate buildRequiredInDataversePredicate(CriteriaBuilder criteriaBuilder, Root<DatasetFieldType> datasetFieldTypeRoot) {
1018+
private Predicate buildFieldRequiredInTheInstallationPredicate(CriteriaBuilder criteriaBuilder, Root<DatasetFieldType> datasetFieldTypeRoot) {
9661019
// Predicate to check if the current DatasetFieldType is required.
9671020
Predicate isRequired = criteriaBuilder.isTrue(datasetFieldTypeRoot.get("required"));
9681021

0 commit comments

Comments
 (0)