Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/release-notes/8535-metadata-types-static-facet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Adding new static search facet: Metadata Types
A new static search facet has been added to the search side panel. This new facet is called "Metadata Types" and is driven from metadata blocks. When a metadata field value is inserted into a dataset, an entry for the metadata block it belongs to is added to this new facet.

This new facet needs to be configured for it to appear on the search side panel. The configuration assigns to a dataverse what metadata blocks to show. The configuration is inherited by child dataverses.

To configure the new facet, use the Metadata Block Facet API: <https://guides.dataverse.org/en/latest/api/native-api.html#set-metadata-block-facet-for-a-dataverse-collection>
1 change: 1 addition & 0 deletions doc/sphinx-guides/source/_static/api/dataverse-facets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["authorName", "authorAffiliation"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["socialscience", "geospatial"]
73 changes: 70 additions & 3 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,82 @@ Assign search facets for a given Dataverse collection identified by ``id``:
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H X-Dataverse-key:$API_TOKEN" -X POST $SERVER_URL/api/dataverses/$ID/facets --upload-file facets.json
curl -H X-Dataverse-key:$API_TOKEN" -X POST $SERVER_URL/api/dataverses/$ID/facets --upload-file dataverse-facets.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 -X POST https://demo.dataverse.org/api/dataverses/root/facets --upload-file facets.json
curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST https://demo.dataverse.org/api/dataverses/root/facets --upload-file dataverse-facets.json

Where ``facets.json`` contains a JSON encoded list of metadata keys (e.g. ``["authorName","authorAffiliation"]``).
Where :download:`dataverse-facets.json <../_static/api/dataverse-facets.json>` contains a JSON encoded list of metadata keys (e.g. ``["authorName","authorAffiliation"]``).

List Metadata Block Facets Configured for a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

|CORS| List the metadata block facet configuration with all the metadata block configured for a given Dataverse collection ``id``:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H X-Dataverse-key:$API_TOKEN $SERVER_URL/api/dataverses/$ID/metadatablockfacets

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/metadatablockfacets

Set Metadata Block Facets for a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sets the metadata blocks that will appear in the ``Dataset Features`` facet category for a given Dataverse collection identified by ``id``.

In order to set or clear the metadata blocks for a collection, you must first :ref:`set the metadata block facet root to true<metadata-block-facet-root-api>`.

To clear the metadata blocks set by a parent collection, submit an empty array (e.g. ``[]``):

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H X-Dataverse-key:$API_TOKEN" -X POST -H "Content-type:application/json" $SERVER_URL/api/dataverses/$ID/metadatablockfacets --upload-file metadata-block-facets.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 -X POST -H "Content-type:application/json" https://demo.dataverse.org/api/dataverses/root/metadatablockfacets --upload-file metadata-block-facets.json

Where :download:`metadata-block-facets.json <../_static/api/metadata-block-facets.json>` contains a JSON encoded list of metadata block names (e.g. ``["socialscience","geospatial"]``). This endpoint supports an empty list (e.g. ``[]``)

.. _metadata-block-facet-root-api:

Configure a Dataverse Collection to Inherit Its Metadata Block Facets from Its Parent
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Set whether the Dataverse collection is a metadata block facet root, or does it uses its parent metadata block facets. Possible values are ``true`` and ``false`` (both are valid JSON expressions).

When updating the root to false, it will clear any metadata block facets from the collection. When updating to true, it will copy the metadata block facets from the parent collection:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H X-Dataverse-key:$API_TOKEN -X POST -H "Content-type:application/json" $SERVER_URL/api/dataverses/$ID/metadatablockfacets/isRoot -d 'true'

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST -H "Content-type:application/json" https://demo.dataverse.org/api/dataverses/root/metadatablockfacets/isRoot -d 'true'

Create a New Role in a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
27 changes: 25 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/Dataverse.java
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,31 @@ public boolean isHarvested() {
return harvestingClient != null;
}
*/


private boolean metadataBlockFacetRoot;

public boolean isMetadataBlockFacetRoot() {
return metadataBlockFacetRoot;
}

public void setMetadataBlockFacetRoot(boolean metadataBlockFacetRoot) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be set to false when you create a new Dataverse? Also for the root dataverse do we need to set it to true - since it doesn't have an owner? And it seems as if the only way to get a child dataverse to not inherit facets from its parent would be to give it its own facets via the api, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be false by default. Because it is a boolean primitive, the default value is false. I was following the same pattern as with metadataBlockRoot and facetRoot.

As for the root dataverse, technically, there is no need to set it to true because of the way getMetadataBlockFacetRoot was coded (checking for the owner). But if metadataBlockRoot and others are set to true in the root dataverse, then I guess it should be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sekmiller are you happy with the explanation?
Do you want me to change anything regarding metadataBlockFacetRoot?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adaybujeda - yes, sorry I was away for a few days. I just want to do another pass through the code, but I will probably send it along to QA shortly.

this.metadataBlockFacetRoot = metadataBlockFacetRoot;
}

@OneToMany(mappedBy = "dataverse",cascade={ CascadeType.REMOVE, CascadeType.MERGE,CascadeType.PERSIST }, orphanRemoval=true)
private List<DataverseMetadataBlockFacet> metadataBlockFacets = new ArrayList<>();

public List<DataverseMetadataBlockFacet> getMetadataBlockFacets() {
if (isMetadataBlockFacetRoot() || getOwner() == null) {
return metadataBlockFacets;
} else {
return getOwner().getMetadataBlockFacets();
}
}

public void setMetadataBlockFacets(List<DataverseMetadataBlockFacet> metadataBlockFacets) {
this.metadataBlockFacets = metadataBlockFacets;
}

public List<Guestbook> getParentGuestbooks() {
List<Guestbook> retList = new ArrayList<>();
Dataverse testDV = this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package edu.harvard.iq.dataverse;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Objects;

/**
*
* @author adaybujeda
*/
@Entity
@Table(indexes = {@Index(columnList="dataverse_id")
, @Index(columnList="metadatablock_id")})
public class DataverseMetadataBlockFacet implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "dataverse_id")
private Dataverse dataverse;

@ManyToOne
@JoinColumn(name = "metadatablock_id")
private MetadataBlock metadataBlock;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Dataverse getDataverse() {
return dataverse;
}

public void setDataverse(Dataverse dataverse) {
this.dataverse = dataverse;
}

public MetadataBlock getMetadataBlock() {
return metadataBlock;
}

public void setMetadataBlock(MetadataBlock metadataBlock) {
this.metadataBlock = metadataBlock;
}

@Override
public int hashCode() {
int hash = 0;
hash += (this.id != null ? this.id.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
if (!(object instanceof DataverseMetadataBlockFacet)) {
return false;
}
DataverseMetadataBlockFacet other = (DataverseMetadataBlockFacet) object;
return !(!Objects.equals(this.id, other.id) && (this.id == null || !this.id.equals(other.id)));
}

@Override
public String toString() {
return String.format("edu.harvard.iq.dataverse.DataverseMetadataBlockFacet[ id=%s ]", id);
}

}

14 changes: 11 additions & 3 deletions src/main/java/edu/harvard/iq/dataverse/MetadataBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,18 @@ public String toString() {
return "edu.harvard.iq.dataverse.MetadataBlock[ id=" + id + " ]";
}

public String getLocaleDisplayName()
{
public String getLocaleDisplayName() {
return getLocaleValue("metadatablock.displayName");
}

public String getLocaleDisplayFacet() {
return getLocaleValue("metadatablock.displayFacet");
}

// Visible for testing
String getLocaleValue(String metadataBlockKey) {
try {
return BundleUtil.getStringFromPropertyFile("metadatablock.displayName", getName());
return BundleUtil.getStringFromPropertyFile(metadataBlockKey, getName());
} catch (MissingResourceException e) {
return displayName;
}
Expand Down
83 changes: 78 additions & 5 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseFacet;
import edu.harvard.iq.dataverse.DataverseContact;
import edu.harvard.iq.dataverse.DataverseMetadataBlockFacet;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.api.datadeposit.SwordServiceBean;
import edu.harvard.iq.dataverse.api.dto.DataverseMetadataBlockFacetDTO;
import edu.harvard.iq.dataverse.authorization.DataverseRole;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.DvObjectContainer;
import edu.harvard.iq.dataverse.GlobalId;
import edu.harvard.iq.dataverse.GuestbookResponseServiceBean;
import edu.harvard.iq.dataverse.GuestbookServiceBean;
import edu.harvard.iq.dataverse.MetadataBlock;
import edu.harvard.iq.dataverse.RoleAssignment;
import static edu.harvard.iq.dataverse.api.AbstractApiBean.error;
import edu.harvard.iq.dataverse.api.dto.ExplicitGroupDTO;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
Expand All @@ -41,6 +41,7 @@
import edu.harvard.iq.dataverse.engine.command.impl.DeleteDataverseCommand;
import edu.harvard.iq.dataverse.engine.command.impl.DeleteDataverseLinkingDataverseCommand;
import edu.harvard.iq.dataverse.engine.command.impl.DeleteExplicitGroupCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateMetadataBlockFacetRootCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetDataverseCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetDataverseStorageSizeCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetExplicitGroupCommand;
Expand All @@ -49,6 +50,7 @@
import edu.harvard.iq.dataverse.engine.command.impl.ListDataverseContentCommand;
import edu.harvard.iq.dataverse.engine.command.impl.ListExplicitGroupsCommand;
import edu.harvard.iq.dataverse.engine.command.impl.ListFacetsCommand;
import edu.harvard.iq.dataverse.engine.command.impl.ListMetadataBlockFacetsCommand;
import edu.harvard.iq.dataverse.engine.command.impl.ListMetadataBlocksCommand;
import edu.harvard.iq.dataverse.engine.command.impl.ListRoleAssignments;
import edu.harvard.iq.dataverse.engine.command.impl.ListRolesCommand;
Expand All @@ -62,14 +64,14 @@
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseDefaultContributorRoleCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseMetadataBlocksCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateExplicitGroupCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateMetadataBlockFacetsCommand;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.ConstraintViolationUtil;
import edu.harvard.iq.dataverse.util.StringUtil;
import static edu.harvard.iq.dataverse.util.StringUtil.nonEmpty;

import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
import edu.harvard.iq.dataverse.util.json.JsonLDTerm;
import edu.harvard.iq.dataverse.util.json.JsonParseException;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.brief;
import java.io.StringReader;
Expand All @@ -91,7 +93,6 @@
import javax.json.JsonValue;
import javax.json.JsonValue.ValueType;
import javax.json.stream.JsonParsingException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
Expand All @@ -114,9 +115,9 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
Expand Down Expand Up @@ -713,6 +714,78 @@ public Response setFacets(@PathParam("identifier") String dvIdtf, String facetId
}
}

@GET
@Path("{identifier}/metadatablockfacets")
@Produces(MediaType.APPLICATION_JSON)
public Response listMetadataBlockFacets(@PathParam("identifier") String dvIdtf) {
try {
User u = findUserOrDie();
DataverseRequest request = createDataverseRequest(u);
Dataverse dataverse = findDataverseOrDie(dvIdtf);
List<DataverseMetadataBlockFacet> metadataBlockFacets = Optional.ofNullable(execCommand(new ListMetadataBlockFacetsCommand(request, dataverse))).orElse(Collections.emptyList());
List<DataverseMetadataBlockFacetDTO.MetadataBlockDTO> metadataBlocksDTOs = metadataBlockFacets.stream()
.map(item -> new DataverseMetadataBlockFacetDTO.MetadataBlockDTO(item.getMetadataBlock().getName(), item.getMetadataBlock().getLocaleDisplayFacet()))
.collect(Collectors.toList());
DataverseMetadataBlockFacetDTO response = new DataverseMetadataBlockFacetDTO(dataverse.getId(), dataverse.getAlias(), dataverse.isMetadataBlockFacetRoot(), metadataBlocksDTOs);
return Response.ok(response).build();
} catch (WrappedResponse e) {
return e.getResponse();
}
}

@POST
@Path("{identifier}/metadatablockfacets")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response setMetadataBlockFacets(@PathParam("identifier") String dvIdtf, List<String> metadataBlockNames) {
try {
Dataverse dataverse = findDataverseOrDie(dvIdtf);

if(!dataverse.isMetadataBlockFacetRoot()) {
return badRequest(String.format("Dataverse: %s must have metadata block facet root set to true", dvIdtf));
}

List<DataverseMetadataBlockFacet> metadataBlockFacets = new LinkedList<>();
for(String metadataBlockName: metadataBlockNames) {
MetadataBlock metadataBlock = findMetadataBlock(metadataBlockName);
if (metadataBlock == null) {
return badRequest(String.format("Invalid metadata block name: %s", metadataBlockName));
}

DataverseMetadataBlockFacet metadataBlockFacet = new DataverseMetadataBlockFacet();
metadataBlockFacet.setDataverse(dataverse);
metadataBlockFacet.setMetadataBlock(metadataBlock);
metadataBlockFacets.add(metadataBlockFacet);
}

execCommand(new UpdateMetadataBlockFacetsCommand(createDataverseRequest(findUserOrDie()), dataverse, metadataBlockFacets));
return ok(String.format("Metadata block facets updated. DataverseId: %s blocks: %s", dvIdtf, metadataBlockNames));

} catch (WrappedResponse ex) {
return ex.getResponse();
}
}

@POST
@Path("{identifier}/metadatablockfacets/isRoot")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateMetadataBlockFacetsRoot(@PathParam("identifier") String dvIdtf, String body) {
try {
final boolean blockFacetsRoot = parseBooleanOrDie(body);
Dataverse dataverse = findDataverseOrDie(dvIdtf);
if(dataverse.isMetadataBlockFacetRoot() == blockFacetsRoot) {
return ok(String.format("No update needed, dataverse already consistent with new value. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot));
}

execCommand(new UpdateMetadataBlockFacetRootCommand(createDataverseRequest(findUserOrDie()), dataverse, blockFacetsRoot));
return ok(String.format("Metadata block facets root updated. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot));

} catch (WrappedResponse ex) {
return ex.getResponse();
}
}

// FIXME: This listContent method is way too optimistic, always returning "ok" and never "error".
// TODO: Investigate why there was a change in the timeframe of when pull request #4350 was merged
// (2438-4295-dois-for-files branch) such that a contributor API token no longer allows this method
Expand Down
Loading