Skip to content

Commit

Permalink
Merge 3d59092 into a6b2738
Browse files Browse the repository at this point in the history
  • Loading branch information
sekmiller committed Apr 16, 2024
2 parents a6b2738 + 3d59092 commit f78e5fb
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/release-notes/10242-add-feature-dv-api
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New api endpoints have been added to allow you to add or remove featured collections from a dataverse collection.
64 changes: 64 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,70 @@ The fully expanded example above (without environment variables) looks like this
Note: you must have "Add Dataset" permission in the given collection to invoke this endpoint.

List Featured Collections for a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The response is a JSON array of the alias strings of the featured collections of a given Dataverse collection identified by ``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" -X GET "$SERVER_URL/api/dataverses/$ID/featured"
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 GET "https://demo.dataverse.org/api/dataverses/root/featured"
Set Featured Collections for a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Add featured collections to a given Dataverse collection identified by ``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" -X POST "$SERVER_URL/api/dataverses/$ID/featured" --upload-file collection-alias.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/featured" --upload-file collection-alias.json
Where collection-alias.json contains a JSON encoded list of collections aliases to be featured (e.g. ``["collection1-alias","collection2-alias"]``).

Note: You must have "Edit Dataverse" permission in the given Dataverse to invoke this endpoint. You may only feature collections that are published and owned by or linked to the featuring collection. Also, using this endpoint will only add new featured collections it will not remove collections that have already been featured.

Remove Featured Collections from a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Remove featured collections from a given Dataverse collection identified by ``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" -X DELETE "$SERVER_URL/api/dataverses/$ID/featured"
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 DELETE "https://demo.dataverse.org/api/dataverses/root/featured"
Note: You must have "Edit Dataverse" permission in the given Dataverse to invoke this endpoint.

.. _create-dataset-command:

Create a Dataset in a Dataverse Collection
Expand Down
116 changes: 116 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseFacet;
import edu.harvard.iq.dataverse.DataverseContact;
import edu.harvard.iq.dataverse.DataverseFeaturedDataverse;
import edu.harvard.iq.dataverse.DataverseLinkingServiceBean;
import edu.harvard.iq.dataverse.DataverseMetadataBlockFacet;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
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.FeaturedDataverseServiceBean;
import edu.harvard.iq.dataverse.GlobalId;
import edu.harvard.iq.dataverse.GuestbookResponseServiceBean;
import edu.harvard.iq.dataverse.GuestbookServiceBean;
Expand Down Expand Up @@ -56,6 +59,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.ListFeaturedCollectionsCommand;
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;
Expand Down Expand Up @@ -136,6 +140,7 @@
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.StreamingOutput;
import java.util.ArrayList;
import javax.xml.stream.XMLStreamException;

/**
Expand Down Expand Up @@ -167,6 +172,12 @@ public class Dataverses extends AbstractApiBean {

@EJB
DataverseServiceBean dataverseService;

@EJB
DataverseLinkingServiceBean linkingService;

@EJB
FeaturedDataverseServiceBean featuredDataverseService;

@EJB
SwordServiceBean swordService;
Expand Down Expand Up @@ -825,6 +836,111 @@ public Response listFacets(@Context ContainerRequestContext crc, @PathParam("ide
return e.getResponse();
}
}


@GET
@AuthRequired
@Path("{identifier}/featured")
/*
Allows user to get the collections that are featured by a given collection
probably more for SPA than end user
*/
public Response getFeaturedDataverses(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String dvAliases) {

try {
User u = getRequestUser(crc);
DataverseRequest r = createDataverseRequest(u);
Dataverse dataverse = findDataverseOrDie(dvIdtf);
JsonArrayBuilder fs = Json.createArrayBuilder();
for (Dataverse f : execCommand(new ListFeaturedCollectionsCommand(r, dataverse))) {
fs.add(f.getAlias());
}
return ok(fs);
} catch (WrappedResponse e) {
return e.getResponse();
}
}


@POST
@AuthRequired
@Path("{identifier}/featured")
/**
* Allows user to set featured dataverses - must have edit dataverse permission
*
*/
public Response setFeaturedDataverses(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String dvAliases) {
List<Dataverse> dvsFromInput = new LinkedList<>();


try {

for (JsonString dvAlias : Util.asJsonArray(dvAliases).getValuesAs(JsonString.class)) {
Dataverse dvToBeFeatured = dataverseService.findByAlias(dvAlias.getString());
if (dvToBeFeatured == null) {
return error(Response.Status.BAD_REQUEST, "Can't find dataverse collection with alias '" + dvAlias + "'");
}
dvsFromInput.add(dvToBeFeatured);
}

if (dvsFromInput.isEmpty()) {
return error(Response.Status.BAD_REQUEST, "Please provide a valid Json array of dataverse collection aliases to be featured.");
}

Dataverse dataverse = findDataverseOrDie(dvIdtf);
List<Dataverse> featuredSource = new ArrayList<>();
List<Dataverse> featuredTarget = new ArrayList<>();
featuredSource.addAll(dataverseService.findAllPublishedByOwnerId(dataverse.getId()));
featuredSource.addAll(linkingService.findLinkedDataverses(dataverse.getId()));
List<DataverseFeaturedDataverse> featuredList = featuredDataverseService.findByDataverseId(dataverse.getId());

if (featuredSource.isEmpty()) {
return error(Response.Status.BAD_REQUEST, "There are no collections avaialble to be featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
}

for (DataverseFeaturedDataverse dfd : featuredList) {
Dataverse fd = dfd.getFeaturedDataverse();
featuredTarget.add(fd);
featuredSource.remove(fd);
}

for (Dataverse test : dvsFromInput) {
if (featuredTarget.contains(test)) {
return error(Response.Status.BAD_REQUEST, "Dataverse collection '" + test.getDisplayName() + "' is already featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
}

if (featuredSource.contains(test)) {
featuredTarget.add(test);
} else {
return error(Response.Status.BAD_REQUEST, "Dataverse collection '" + test.getDisplayName() + "' may not be featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
}

}
// by passing null for Facets and DataverseFieldTypeInputLevel, those are not changed
execCommand(new UpdateDataverseCommand(dataverse, null, featuredTarget, createDataverseRequest(getRequestUser(crc)), null));
return ok("Featured Dataverses of dataverse " + dvIdtf + " updated.");

} catch (WrappedResponse ex) {
return ex.getResponse();
} catch (JsonParsingException jpe){
return error(Response.Status.BAD_REQUEST, "Please provide a valid Json array of dataverse collection aliases to be featured.");
}

}

@DELETE
@AuthRequired
@Path("{identifier}/featured")
public Response deleteFeaturedCollections(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse {
try {
Dataverse dataverse = findDataverseOrDie(dvIdtf);
List<Dataverse> featuredTarget = new ArrayList<>();
execCommand(new UpdateDataverseCommand(dataverse, null, featuredTarget, createDataverseRequest(getRequestUser(crc)), null));
return ok(BundleUtil.getStringFromBundle("dataverses.api.delete.featured.collections.successful"));
} catch (WrappedResponse ex) {
return ex.getResponse();
}
}

@POST
@AuthRequired
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseFeaturedDataverse;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.authorization.Permission;
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;

/**
*
* @author stephenkraffmiller
*/
public class ListFeaturedCollectionsCommand extends AbstractCommand<List<Dataverse>> {

private final Dataverse dv;

public ListFeaturedCollectionsCommand(DataverseRequest aRequest, Dataverse aDataverse) {
super(aRequest, aDataverse);
dv = aDataverse;
}

@Override
public List<Dataverse> execute(CommandContext ctxt) throws CommandException {
List<Dataverse> featuredTarget = new ArrayList<>();
List<DataverseFeaturedDataverse> featuredList = ctxt.featuredDataverses().findByDataverseId(dv.getId());
for (DataverseFeaturedDataverse dfd : featuredList) {
Dataverse fd = dfd.getFeaturedDataverse();
featuredTarget.add(fd);
}
return featuredTarget;

}

@Override
public Map<String, Set<Permission>> getRequiredPermissions() {
return Collections.singletonMap("",
dv.isReleased() ? Collections.<Permission>emptySet()
: Collections.singleton(Permission.ViewUnpublishedDataverse));
}

}
1 change: 1 addition & 0 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,7 @@ dataverses.api.move.dataverse.failure.not.published=Published dataverse may not
dataverses.api.move.dataverse.error.guestbook=Dataset guestbook is not in target dataverse.
dataverses.api.move.dataverse.error.template=Dataverse template is not in target dataverse.
dataverses.api.move.dataverse.error.featured=Dataverse is featured in current dataverse.
dataverses.api.delete.featured.collections.successful=Featured dataverses have been removed
dataverses.api.move.dataverse.error.metadataBlock=Dataverse metadata block is not in target dataverse.
dataverses.api.move.dataverse.error.dataverseLink=Dataverse is linked to target dataverse or one of its parents.
dataverses.api.move.dataverse.error.datasetLink=Dataset is linked to target dataverse or one of its parents.
Expand Down
82 changes: 82 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Files;
import io.restassured.path.json.JsonPath;
import static jakarta.ws.rs.core.Response.Status.OK;
import org.hamcrest.CoreMatchers;
import static org.hamcrest.CoreMatchers.containsString;
import org.hamcrest.Matchers;

public class DataversesIT {
Expand Down Expand Up @@ -750,4 +752,84 @@ public void testListMetadataBlocks() {
listMetadataBlocksResponse = UtilIT.listMetadataBlocks(secondDataverseAlias, true, true, apiToken);
listMetadataBlocksResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());
}

@Test
public void testFeatureDataverse() throws Exception {

Response createUser = UtilIT.createRandomUser();
String apiToken = UtilIT.getApiTokenFromResponse(createUser);

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);

Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken);
assertEquals(200, publishDataverse.getStatusCode());


Response createSubDVToBeFeatured = UtilIT.createSubDataverse(UtilIT.getRandomDvAlias() + "-feature", null, apiToken, dataverseAlias);
String subDataverseAlias = UtilIT.getAliasFromResponse(createSubDVToBeFeatured);

//publish a sub dataverse so that the owner will have something to feature
Response createSubDVToBePublished = UtilIT.createSubDataverse(UtilIT.getRandomDvAlias() + "-pub", null, apiToken, dataverseAlias);
assertEquals(201, createSubDVToBePublished.getStatusCode());
String subDataverseAliasPub = UtilIT.getAliasFromResponse(createSubDVToBePublished);
publishDataverse = UtilIT.publishDataverseViaNativeApi(subDataverseAliasPub, apiToken);
assertEquals(200, publishDataverse.getStatusCode());

//can't feature a dataverse that is unpublished
Response featureSubDVResponseUnpublished = UtilIT.addFeaturedDataverse(dataverseAlias, subDataverseAlias, apiToken);
featureSubDVResponseUnpublished.prettyPrint();
assertEquals(400, featureSubDVResponseUnpublished.getStatusCode());
featureSubDVResponseUnpublished.then().assertThat()
.body(containsString("may not be featured"));

//can't feature a dataverse you don't own
Response featureSubDVResponseNotOwned = UtilIT.addFeaturedDataverse(dataverseAlias, "root", apiToken);
featureSubDVResponseNotOwned.prettyPrint();
assertEquals(400, featureSubDVResponseNotOwned.getStatusCode());
featureSubDVResponseNotOwned.then().assertThat()
.body(containsString("may not be featured"));

//can't feature a dataverse that doesn't exist
Response featureSubDVResponseNotExist = UtilIT.addFeaturedDataverse(dataverseAlias, "dummy-alias-sek-foobar-333", apiToken);
featureSubDVResponseNotExist.prettyPrint();
assertEquals(400, featureSubDVResponseNotExist.getStatusCode());
featureSubDVResponseNotExist.then().assertThat()
.body(containsString("Can't find dataverse collection"));

publishDataverse = UtilIT.publishDataverseViaNativeApi(subDataverseAlias, apiToken);
assertEquals(200, publishDataverse.getStatusCode());

//once published it should work
Response featureSubDVResponse = UtilIT.addFeaturedDataverse(dataverseAlias, subDataverseAlias, apiToken);
featureSubDVResponse.prettyPrint();
assertEquals(OK.getStatusCode(), featureSubDVResponse.getStatusCode());


Response getFeaturedDataverseResponse = UtilIT.getFeaturedDataverses(dataverseAlias, apiToken);
getFeaturedDataverseResponse.prettyPrint();
assertEquals(OK.getStatusCode(), getFeaturedDataverseResponse.getStatusCode());
getFeaturedDataverseResponse.then().assertThat()
.body("data[0]", equalTo(subDataverseAlias));

Response deleteFeaturedDataverseResponse = UtilIT.deleteFeaturedDataverses(dataverseAlias, apiToken);
deleteFeaturedDataverseResponse.prettyPrint();

assertEquals(OK.getStatusCode(), deleteFeaturedDataverseResponse.getStatusCode());
deleteFeaturedDataverseResponse.then().assertThat()
.body(containsString("Featured dataverses have been removed"));

Response deleteSubCollectionResponse = UtilIT.deleteDataverse(subDataverseAlias, apiToken);
deleteSubCollectionResponse.prettyPrint();
assertEquals(OK.getStatusCode(), deleteSubCollectionResponse.getStatusCode());

Response deleteSubCollectionPubResponse = UtilIT.deleteDataverse(subDataverseAliasPub, apiToken);
deleteSubCollectionResponse.prettyPrint();
assertEquals(OK.getStatusCode(), deleteSubCollectionPubResponse.getStatusCode());

Response deleteCollectionResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken);
deleteCollectionResponse.prettyPrint();
assertEquals(OK.getStatusCode(), deleteCollectionResponse.getStatusCode());
}

}

0 comments on commit f78e5fb

Please sign in to comment.