Skip to content
Open
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
12 changes: 12 additions & 0 deletions doc/release-notes/12386-manage-guestbook-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Feature - Manage Guestbook

This feature adds 2 new APIs to help manage Guestbooks and Guestbook Responses.


This API allows the user to make edits to an existing Guestbook, including adding and removing Custom Guestbook Questions.

`curl -PUT -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}" -d "$JSON"`

This API allows the user to retrieve Guestbook Responses for a specific Guestbook within a Collection. Optional pagination parameters can be added to limit the number of results, as this can get very large.

`curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0"`
48 changes: 48 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,28 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1234"

Update a Guestbook for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

Update a Guestbook that can be selected for a Dataset.
You must have "EditDataverse" permission on the Dataverse collection.

.. code-block:: bash

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

curl -PUT -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}" -d "$JSON"

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

.. code-block:: bash

curl -PUT -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1234" -d "$JSON"

Enable or Disable a Guestbook for a Dataverse Collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -1298,6 +1320,32 @@ The fully expanded example above (without environment variables) looks like this

curl -X PUT -d 'true' -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/root/1234"

Retrieve Guestbook Responses for a Guestbook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.

In order to retrieve the Guestbook Responses for a Guestbook within a Dataverse collection, you must know the ID if the Guestbook. This API also supports pagination by passing a page limit and an optional offset (starting point). The resulting Json will include 'Next' and 'Prev' urls for navigation as well as the total number of responses.
The resulting Json will be more detailed than that of the :ref:`download-guestbook-api` CSV response file by including Guestbook metadata as well as Guestbook Response metadata.

.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of ``export`` below.

.. code-block:: bash

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

curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses"
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0"

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/guestbooks/1/responses"
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1/responses?limit10&offset=0"

.. _collection-attributes-api:

Change Collection Attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,19 @@ public List<Long> findAllIds(Long dataverseId) {
}

public List<GuestbookResponse> findAllByGuestbookId(Long guestbookId) {
return findAllByGuestbookId(guestbookId, null, null);
}
public List<GuestbookResponse> findAllByGuestbookId(Long guestbookId, Integer offset, Integer limit) {
if (guestbookId != null) {
TypedQuery<GuestbookResponse> query = em.createQuery("select o from GuestbookResponse as o where o.guestbook.id = " + guestbookId + " order by o.responseTime desc", GuestbookResponse.class);
if (offset != null) {
query.setFirstResult(offset);
}
if (limit != null) {
query.setMaxResults(limit);
}

if (guestbookId == null) {
} else {
return em.createQuery("select o from GuestbookResponse as o where o.guestbook.id = " + guestbookId + " order by o.responseTime desc", GuestbookResponse.class).getResultList();
return query.getResultList();
}
return null;
}
Expand Down
80 changes: 76 additions & 4 deletions src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package edu.harvard.iq.dataverse.api;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.Guestbook;
import edu.harvard.iq.dataverse.GuestbookResponseServiceBean;
import edu.harvard.iq.dataverse.GuestbookServiceBean;
import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
Expand Down Expand Up @@ -31,6 +28,7 @@
import java.util.logging.Logger;

import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;

@Path("guestbooks")
public class Guestbooks extends AbstractApiBean {
Expand Down Expand Up @@ -119,6 +117,80 @@ public Response createGuestbook(@Context ContainerRequestContext crc, @PathParam
}
}

@PUT
@AuthRequired
@Path("{id}")
public Response updateGuestbook(@Context ContainerRequestContext crc, @PathParam("id") Long id, String jsonBody) {
return response( req -> {
Guestbook guestbook = guestbookService.find(id);
if (guestbook != null) {
try {
JsonObject jsonObj = JsonUtil.getJsonObject(jsonBody);
jsonParser().parseGuestbook(jsonObj, guestbook);
} catch (JsonException | JsonParseException ex) {
logger.log(Level.WARNING, "Error parsing guestbook JSON", ex);
return badRequest("Error parsing guestbook JSON");
}
Guestbook newGuestbook = execCommand(new UpdateGuestbookCommand(guestbook, req, guestbook.getDataverse()));
return ok(json(newGuestbook));
} else {
return notFound(BundleUtil.getStringFromBundle("dataset.manageGuestbooks.message.notFound"));
}
}, getRequestUser(crc));
}

@GET
@AuthRequired
@Path("/{id}/responses")
public Response getResponses(@Context ContainerRequestContext crc, @PathParam("id") Long id,
@QueryParam("limit") Integer limit, @QueryParam("offset") Integer offset) {

return response( req -> {
Guestbook guestbook = guestbookService.find(id);
if (guestbook == null) {
return notFound("Guestbook " + id + " not found.");
}
Dataverse dataverse = guestbook.getDataverse();
if (!permissionSvc.request(req).on(dataverse).has(Permission.EditDataverse)) {
return error(Response.Status.FORBIDDEN, "Not authorized");
}
Long totalUsageCount = guestbookService.findCountUsages(guestbook.getId(), dataverse.getId());
Long totalResponseCount = guestbookResponseService.findCountByGuestbookId(guestbook.getId(), dataverse.getId());
guestbook.setUsageCount(totalUsageCount);
guestbook.setResponseCount(totalResponseCount);

List<GuestbookResponse> responses = guestbookResponseService.findAllByGuestbookId(guestbook.getId(), offset, limit);

JsonObjectBuilder guestbookResponseObject = jsonObjectBuilder();
guestbookResponseObject.add("guestbook", JsonPrinter.json(guestbook));

JsonArrayBuilder responseObjects = Json.createArrayBuilder();
for (GuestbookResponse gr : responses) {
responseObjects.add(JsonPrinter.json(gr));
}
guestbookResponseObject.add("responses", responseObjects);

if (limit != null) {
JsonObjectBuilder guestbookPageObject = jsonObjectBuilder();
int thisOffset = offset != null ? offset : 0;
int next = thisOffset + limit;
int prev = thisOffset - limit;

String baseUrl = crc.getUriInfo().getAbsolutePath() + "?limit=" + limit + "&offset=" ;
if (prev >= 0) {
guestbookPageObject.add("previous",baseUrl + prev);
}
if (next < totalResponseCount) {
guestbookPageObject.add("next", baseUrl + next);
}
guestbookPageObject.add("totalResponses", totalResponseCount);

guestbookResponseObject.add("pagination", guestbookPageObject);
}
return ok(guestbookResponseObject);
}, getRequestUser(crc));
}

@PUT
@AuthRequired
@Path("{identifier}/{id}/enabled")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,9 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th

public Guestbook parseGuestbook(JsonObject obj, Guestbook gb) throws JsonParseException {
try {
if (obj.containsKey("id")) {
gb.setId(Long.valueOf(obj.getInt("id")));
}
gb.setName(obj.getString("name", null));
gb.setEnabled(obj.getBoolean("enabled"));
gb.setEmailRequired(obj.getBoolean("emailRequired"));
Expand All @@ -573,6 +576,9 @@ private List<CustomQuestion> parseCustomQuestions(JsonArray customQuestions, Gue
customQuestions.forEach(q -> {
JsonObject obj = q.asJsonObject();
CustomQuestion cq = new CustomQuestion();
if (obj.containsKey("id")) {
cq.setId(Long.valueOf(obj.getInt("id")));
}
cq.setQuestionString(obj.getString("question"));
cq.setRequired(obj.getBoolean("required"));
cq.setDisplayOrder(obj.getInt("displayOrder"));
Expand All @@ -586,6 +592,9 @@ private List<CustomQuestion> parseCustomQuestions(JsonArray customQuestions, Gue
optionValues.forEach(v -> {
JsonObject ov = v.asJsonObject();
CustomQuestionValue cqv = new CustomQuestionValue();
if (ov.containsKey("id")) {
cqv.setId(Long.valueOf(ov.getInt("id")));
}
cqv.setValueString(ov.getString("value"));
cqv.setDisplayOrder(ov.getInt("displayOrder"));
cqv.setCustomQuestion(cq);
Expand Down
58 changes: 51 additions & 7 deletions src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,50 @@ public static JsonObjectBuilder getOwnersFromDvObject(DvObject dvObject){
return getOwnersFromDvObject(dvObject, null);
}

public static JsonObjectBuilder json(GuestbookResponse gbResponse) {
JsonObjectBuilder guestbookResponseObject = jsonObjectBuilder();
if (gbResponse != null) {
guestbookResponseObject.add("id", gbResponse.getId());
guestbookResponseObject.add("dataset", gbResponse.getDataset().getDisplayName());
guestbookResponseObject.add("datasetPid", gbResponse.getDataset().getIdentifier());
guestbookResponseObject.add("date", format(gbResponse.getResponseTime()));
guestbookResponseObject.add("type", gbResponse.getEventType());
guestbookResponseObject.add("fileName", gbResponse.getDataFile().getDisplayName());
guestbookResponseObject.add("fileId", gbResponse.getDataFile().getId());
if (gbResponse.getDataFile().getGlobalId() != null) {
guestbookResponseObject.add("filePid", gbResponse.getDataFile().getGlobalId().asString());
}
guestbookResponseObject.add("userName", gbResponse.getAuthenticatedUser() != null ? gbResponse.getAuthenticatedUser().getUserIdentifier() : "Guest");
if (gbResponse.getEmail() != null) {
guestbookResponseObject.add("email", gbResponse.getEmail());
}
if (gbResponse.getInstitution() != null) {
guestbookResponseObject.add("institution", gbResponse.getInstitution());
}
if (gbResponse.getPosition() != null) {
guestbookResponseObject.add("position", gbResponse.getPosition());
}
final List<CustomQuestionResponse> cqResponses = gbResponse.getCustomQuestionResponses();
if (cqResponses != null && !cqResponses.isEmpty()) {
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
for (CustomQuestionResponse cqResponse : cqResponses) {
JsonObjectBuilder cqObj = jsonObjectBuilder();
cqObj.add("question", cqResponse.getCustomQuestion().getQuestionString());
cqObj.add("response", cqResponse.getResponse());
customQuestions.add(cqObj);
}
guestbookResponseObject.add("customQuestions", customQuestions);
}
}
return guestbookResponseObject;
}

public static JsonObjectBuilder json(Guestbook guestbook) {
JsonObjectBuilder guestbookObject = jsonObjectBuilder();
if (guestbook != null) {
guestbookObject.add("id", guestbook.getId());
if (guestbook.getId() != null) {
guestbookObject.add("id", guestbook.getId());
}
guestbookObject.add("name", guestbook.getName());
guestbookObject.add("enabled", guestbook.isEnabled());
guestbookObject.add("emailRequired", guestbook.isEmailRequired());
Expand All @@ -429,15 +469,15 @@ public static JsonObjectBuilder json(Guestbook guestbook) {
if (guestbook.getResponseCount() != null) {
guestbookObject.add("responseCount", guestbook.getResponseCount());
}
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
if (guestbook.getCustomQuestions() != null) {
if (guestbook.getCustomQuestions() != null && !guestbook.getCustomQuestions().isEmpty()) {
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
for (CustomQuestion cq : guestbook.getCustomQuestions()) {
customQuestions.add(json(cq));
}
guestbookObject.add("customQuestions", customQuestions);
}
guestbookObject.add("customQuestions", customQuestions);
if (guestbook.getCreateTime() != null) {
guestbookObject.add("createTime", guestbook.getCreateTime().toString());
guestbookObject.add("createTime", format(guestbook.getCreateTime()));
}
if (guestbook.getDataverse() != null) {
guestbookObject.add("dataverseId", guestbook.getDataverse().getId());
Expand All @@ -447,7 +487,9 @@ public static JsonObjectBuilder json(Guestbook guestbook) {
}
public static JsonObjectBuilder json(CustomQuestion customQuestion) {
JsonObjectBuilder customQuestionObject = jsonObjectBuilder();
customQuestionObject.add("id", customQuestion.getId());
if (customQuestion.getId() != null) {
customQuestionObject.add("id", customQuestion.getId());
}
customQuestionObject.add("question", customQuestion.getQuestionString());
customQuestionObject.add("required", customQuestion.isRequired());
customQuestionObject.add("displayOrder", customQuestion.getDisplayOrder());
Expand All @@ -457,7 +499,9 @@ public static JsonObjectBuilder json(CustomQuestion customQuestion) {
JsonArrayBuilder customQuestionsValues = Json.createArrayBuilder();
for (CustomQuestionValue value : customQuestion.getCustomQuestionValues()) {
JsonObjectBuilder customQuestionValueObject = jsonObjectBuilder();
customQuestionValueObject.add("id", value.getId());
if (value.getId() != null) {
customQuestionValueObject.add("id", value.getId());
}
customQuestionValueObject.add("value", value.getValueString());
customQuestionValueObject.add("displayOrder", value.getDisplayOrder());
customQuestionsValues.add(customQuestionValueObject);
Expand Down
26 changes: 26 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -4105,6 +4105,32 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars
.statusCode(OK.getStatusCode())
.body("data[0].usageCount", is(1))
.body("data[0].responseCount", is(17));

// Test Get All Responses
Response guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), null, null, ownerApiToken);
guestbookListResponses.prettyPrint();
guestbookListResponses.then().assertThat()
.statusCode(OK.getStatusCode());
JsonPath jsonPath = JsonPath.from(guestbookListResponses.body().asString());
int totalCount = jsonPath.getList("data.responses").size();

// Test Get Responses with pagination
int pages = 4; // total should be 17. set to 4 pages
int limit = (totalCount / pages) + 1; // should be 5 per page. we should see 5, 5, 5, 2
int pagedTotalCount = 0;
int totalCountFromJson = 0;
for (int i = 0; i < pages; i++) {
int offset = limit * i;
guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), offset, limit, ownerApiToken);
guestbookListResponses.prettyPrint();
jsonPath = JsonPath.from(guestbookListResponses.body().asString());
pagedTotalCount += jsonPath.getList("data.responses").size();
totalCountFromJson = jsonPath.getInt("data.pagination.totalResponses");
// 'No duplicate ids' was manually verified. Just make sure the count is good. If there were duplicates the count would be high
}
// verify all counts are good and equal
assertEquals(totalCount, pagedTotalCount);
assertEquals(pagedTotalCount, totalCountFromJson);
}

@Test
Expand Down
Loading