From d25756d5029b440afd8d4877f977cac82f2e95ac Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:08:20 -0400 Subject: [PATCH 1/3] Add usage and response counts to get guestbook --- .../edu/harvard/iq/dataverse/api/Guestbooks.java | 11 ++++++++++- .../iq/dataverse/util/json/JsonPrinter.java | 6 ++++++ .../edu/harvard/iq/dataverse/api/FilesIT.java | 15 +++++++++++++++ .../java/edu/harvard/iq/dataverse/api/UtilIT.java | 4 +++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java b/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java index e95d7fd8485..18660b4a635 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java @@ -2,6 +2,7 @@ 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.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.Permission; @@ -38,6 +39,8 @@ public class Guestbooks extends AbstractApiBean { @EJB GuestbookServiceBean guestbookService; + @EJB + GuestbookResponseServiceBean guestbookResponseService; @GET @AuthRequired @@ -57,9 +60,10 @@ public Response getGuestbook(@Context ContainerRequestContext crc, @PathParam("i @GET @AuthRequired @Path("{identifier}/list") - public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier) { + public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier, @QueryParam("ignoreStats") boolean ignoreStats) { return response( req -> { Dataverse dataverse = findDataverseOrDie(identifier); + final Long dataverseId = dataverse.getId(); if (!permissionSvc.request(req).on(dataverse).has(Permission.EditDataverse)) { return error(Response.Status.FORBIDDEN, "Not authorized"); } @@ -67,6 +71,11 @@ public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam(" JsonArrayBuilder guestbookArray = Json.createArrayBuilder(); JsonPrinter jsonPrinter = new JsonPrinter(); for (Guestbook gb : guestbooks) { + // default is to include the stats. Ignore the stats for a faster reply if they are not needed + if (!ignoreStats) { + gb.setUsageCount(guestbookService.findCountUsages(gb.getId(), dataverseId)); + gb.setResponseCount(guestbookResponseService.findCountByGuestbookId(gb.getId(), dataverseId)); + } guestbookArray.add(jsonPrinter.json(gb)); } return ok(guestbookArray); 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 e9b5f545663..729596fb3d4 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 @@ -423,6 +423,12 @@ public static JsonObjectBuilder json(Guestbook guestbook) { guestbookObject.add("nameRequired", guestbook.isNameRequired()); guestbookObject.add("institutionRequired", guestbook.isInstitutionRequired()); guestbookObject.add("positionRequired", guestbook.isPositionRequired()); + if (guestbook.getUsageCount() != null) { + guestbookObject.add("usageCount", guestbook.getUsageCount()); + } + if (guestbook.getResponseCount() != null) { + guestbookObject.add("responseCount", guestbook.getResponseCount()); + } JsonArrayBuilder customQuestions = Json.createArrayBuilder(); if (guestbook.getCustomQuestions() != null) { for (CustomQuestion cq : guestbook.getCustomQuestions()) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 1e06bd4eed4..8833da48d89 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -3983,6 +3983,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars .statusCode(BAD_REQUEST.getStatusCode()); String guestbookResponseForGuest = guestbookResponse.replace("\"guestbookResponse\": {", "\"guestbookResponse\": { \"name\":\"My Name\", \"email\":\"myemail@example.com\", \"position\":\"My Position\", \"institution\":\"My Institution\","); + // With GuestbookResponse. Guest user doesn't have the required Name, etc. So we will add those to the Guestbook Response downloadResponse = UtilIT.postDownloadFile(fileId4, guestbookResponseForGuest); downloadResponse.prettyPrint(); @@ -4008,6 +4009,12 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars .statusCode(OK.getStatusCode()); signedUrl = UtilIT.getSignedUrlFromResponse(downloadResponse); + // Verify that the Guestbook Response is persisted + Response guestbookResponseResponse = UtilIT.getGuestbookResponses(dataverseAlias, guestbook.getId(), ownerApiToken); + guestbookResponseResponse.then().assertThat() + .statusCode(OK.getStatusCode()); + assertTrue(guestbookResponseResponse.prettyPrint().contains("What color car do you drive,Yellow")); + // Download the file using the signed url signedUrlResponse = get(signedUrl); assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); @@ -4053,6 +4060,14 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars signedUrl = UtilIT.getSignedUrlFromResponse(downloadResponse); signedUrlResponse = get(signedUrl); assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); + + // Verify that the guestbook has proper stats + Response guestbookListResponse = UtilIT.getGuestbooks(dataverseAlias, ownerApiToken); + guestbookListResponse.prettyPrint(); + guestbookListResponse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].usageCount", is(1)) + .body("data[0].responseCount", is(16)); } @Test 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 d70b8f2a235..744854fe025 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1267,7 +1267,9 @@ static Response downloadFileOriginal(Integer fileId, String apiToken) { static Response getDownloadFileUrlWithGuestbookResponse(Integer fileId, String apiToken, String body) { RequestSpecification requestSpecification = given(); - requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + if (apiToken != null) { + requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + } if (body != null) { requestSpecification.body(body); } From 51d145e4243d9bd7f6f0c16cc59e01efcbbcce1a Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:22:37 -0400 Subject: [PATCH 2/3] add docs --- .../12260-get-guestbook-with-usage-and-response-counts.md | 3 +++ doc/sphinx-guides/source/api/native-api.rst | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md diff --git a/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md b/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md new file mode 100644 index 00000000000..14a751bad78 --- /dev/null +++ b/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md @@ -0,0 +1,3 @@ +## API Enhancement +API call to `/api/guestbooks/{dataverseAlias}/list` will now include `"usageCount":#` and `"responseCount":#` in the response. +By adding the query param "ignoreStats=true" these values can be excluded for faster response. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 8fe87ad5e65..c36e9cf39a1 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1239,6 +1239,8 @@ 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/root/list" +.. note:: By adding the query param "ignoreStats=true" `usageCount` and `responseCount` values can be excluded for faster response. + Get a Guestbook for a Dataverse Collection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From f3bfe55577d7c8f38d6c879961be86bc7f0082b0 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:56:48 -0400 Subject: [PATCH 3/3] change ignoreStats to includeStats --- .../12260-get-guestbook-with-usage-and-response-counts.md | 3 +-- doc/sphinx-guides/source/api/native-api.rst | 2 +- src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java | 5 ++--- src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java | 2 +- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 6 +++++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md b/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md index 14a751bad78..8f2a4e58d24 100644 --- a/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md +++ b/doc/release-notes/12260-get-guestbook-with-usage-and-response-counts.md @@ -1,3 +1,2 @@ ## API Enhancement -API call to `/api/guestbooks/{dataverseAlias}/list` will now include `"usageCount":#` and `"responseCount":#` in the response. -By adding the query param "ignoreStats=true" these values can be excluded for faster response. +API call to `/api/guestbooks/{dataverseAlias}/list` can now include `"usageCount":#` and `"responseCount":#` in the response by adding the query param "includeStats=true". diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index c36e9cf39a1..d9710dd08d7 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1239,7 +1239,7 @@ 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/root/list" -.. note:: By adding the query param "ignoreStats=true" `usageCount` and `responseCount` values can be excluded for faster response. +.. note:: By adding the query param "includeStats=true" `usageCount` and `responseCount` values can be added to the response. Get a Guestbook for a Dataverse Collection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java b/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java index 18660b4a635..d1a929c8f21 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java @@ -60,7 +60,7 @@ public Response getGuestbook(@Context ContainerRequestContext crc, @PathParam("i @GET @AuthRequired @Path("{identifier}/list") - public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier, @QueryParam("ignoreStats") boolean ignoreStats) { + public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier, @QueryParam("includeStats") boolean includeStats) { return response( req -> { Dataverse dataverse = findDataverseOrDie(identifier); final Long dataverseId = dataverse.getId(); @@ -71,8 +71,7 @@ public Response getGuestbooks(@Context ContainerRequestContext crc, @PathParam(" JsonArrayBuilder guestbookArray = Json.createArrayBuilder(); JsonPrinter jsonPrinter = new JsonPrinter(); for (Guestbook gb : guestbooks) { - // default is to include the stats. Ignore the stats for a faster reply if they are not needed - if (!ignoreStats) { + if (includeStats) { gb.setUsageCount(guestbookService.findCountUsages(gb.getId(), dataverseId)); gb.setResponseCount(guestbookResponseService.findCountByGuestbookId(gb.getId(), dataverseId)); } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java index 8833da48d89..6503a40ef58 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4062,7 +4062,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); // Verify that the guestbook has proper stats - Response guestbookListResponse = UtilIT.getGuestbooks(dataverseAlias, ownerApiToken); + Response guestbookListResponse = UtilIT.getGuestbooks(dataverseAlias, ownerApiToken, true); guestbookListResponse.prettyPrint(); guestbookListResponse.then().assertThat() .statusCode(OK.getStatusCode()) 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 744854fe025..6a28b223700 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -612,9 +612,13 @@ static Response getGuestbook(Long guestbookId, String apiToken) { } static Response getGuestbooks(String dataverseAlias, String apiToken) { + return getGuestbooks(dataverseAlias, apiToken, false); + } + static Response getGuestbooks(String dataverseAlias, String apiToken, boolean includeStats) { + String params = "?includeStats=" + includeStats; RequestSpecification requestSpec = given() .header(API_TOKEN_HTTP_HEADER, apiToken); - return requestSpec.get("/api/guestbooks/" + dataverseAlias + "/list" ); + return requestSpec.get("/api/guestbooks/" + dataverseAlias + "/list" + params ); } static Response enableGuestbook(String dataverseAlias, Long guestbookId, String apiToken, String enable) {