From 0fede7bd985f83bb8fe6125387bea5b7ddbbcb32 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:41:50 -0400 Subject: [PATCH 1/8] Add Guestbook Response Sorting by fields --- .../12524-sort-guestbook-responses.md | 2 + doc/sphinx-guides/source/api/native-api.rst | 14 ++++- .../GuestbookResponseServiceBean.java | 57 +++++++++++++++---- .../harvard/iq/dataverse/api/Guestbooks.java | 13 ++--- .../edu/harvard/iq/dataverse/api/FilesIT.java | 2 +- .../iq/dataverse/api/GuestbooksIT.java | 16 +++++- .../edu/harvard/iq/dataverse/api/UtilIT.java | 9 +++ 7 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 doc/release-notes/12524-sort-guestbook-responses.md diff --git a/doc/release-notes/12524-sort-guestbook-responses.md b/doc/release-notes/12524-sort-guestbook-responses.md new file mode 100644 index 00000000000..aca1bd9e08c --- /dev/null +++ b/doc/release-notes/12524-sort-guestbook-responses.md @@ -0,0 +1,2 @@ +## Feature ## +Added the ability to sort the Guestbook Responses in API /api/guestbooks/{id}/responses by Dataset Name, Event Type, File Name, User Name, and Response Date diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 582891e0861..859071f7448 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1443,6 +1443,16 @@ 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 Responses can be sorted by specifying one of the following in query parameter 'sort' and 'order': + +* ``dataset``: Dataset Name +* ``type``: Event Type +* ``file``: File Name +* ``user``: User Name +* ``date``: Response Date (Default) + +To sort in reverse order you can add ``&order=desc``. + 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. @@ -1454,14 +1464,14 @@ The resulting Json will be more detailed than that of the :ref:`download-guestbo 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" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0&sort=dataset&order=asc" 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" + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1/responses?limit10&offset=0&sort=type&order=desc .. _collection-attributes-api: diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 8ddda6a2a7e..01e7859f53a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -8,6 +8,7 @@ import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.externaltools.ExternalTool; +import edu.harvard.iq.dataverse.search.SortBy; import edu.harvard.iq.dataverse.util.StringUtil; import jakarta.ejb.EJB; import jakarta.ejb.Stateless; @@ -19,6 +20,7 @@ import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.*; import org.apache.commons.text.StringEscapeUtils; import java.io.IOException; @@ -110,24 +112,57 @@ public List findAllIds(Long dataverseId) { return em.createQuery("select o.id from GuestbookResponse o, Dataset d where o.dataset.id = d.id and d.owner.id = " + dataverseId + " order by o.responseTime desc", Long.class).getResultList(); } - public List findAllByGuestbookId(Long guestbookId) { - return findAllByGuestbookId(guestbookId, null, null); + private Order getOrderBy(CriteriaBuilder cb, Path pathObj, boolean isDescending) { + return isDescending ? cb.desc(pathObj) : cb.asc(pathObj); } - public List findAllByGuestbookId(Long guestbookId, Integer offset, Integer limit) { + public List findAllByGuestbookId(Long guestbookId, String sortField, String sortOrder, Integer offset, Integer limit) { if (guestbookId != null) { - TypedQuery 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); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(GuestbookResponse.class); + Root guestbookResponseRoot = cq.from(GuestbookResponse.class); + + boolean isDescending = sortOrder != null && sortOrder.equalsIgnoreCase(SortBy.DESCENDING); + Order order; + String orderField = (sortField == null) ? "" : sortField.toLowerCase(); + switch(orderField) { + case "dataset": + Join datasetVersionJoin = guestbookResponseRoot.join("datasetVersion", JoinType.INNER); + Join datasetFieldJoin = datasetVersionJoin.join("datasetFields", JoinType.INNER); + Join datasetFieldTypeJoin = datasetFieldJoin.join("datasetFieldType", JoinType.INNER); + Join datasetFieldValueJoin = datasetFieldJoin.join("datasetFieldValues", JoinType.INNER); + datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), 1)); // 1 -> title TODO get this instead of hard coding it + order = getOrderBy(cb, datasetFieldValueJoin.get("value"), isDescending); + break; + case "type": + order = getOrderBy(cb, guestbookResponseRoot.get("eventType"), isDescending); + break; + case "file": + Join dataFileJoin = guestbookResponseRoot.join("dataFile", JoinType.INNER); + order = getOrderBy(cb, dataFileJoin.get("fileMetadatas").get("label"), isDescending); + break; + case "user": + order = getOrderBy(cb, guestbookResponseRoot.get("name"), isDescending); + break; + default: + order = getOrderBy(cb, guestbookResponseRoot.get("responseTime"), isDescending); } - return query.getResultList(); + cq.where(cb.equal(guestbookResponseRoot.get("guestbook").get("id"), guestbookId)); + cq.orderBy(order); + + int firstResult = offset == null ? 0 : offset; + int pageSize = limit == null ? 10 : limit; + + List result = em.createQuery(cq) + .setFirstResult(firstResult) + .setMaxResults(pageSize) + .getResultList(); + + return result; } return null; } - + /* This method is used for streaming downloads of guestbook responses, in CSV format, both for individual guestbooks, and for entire dataverses 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 1867b0b515b..f2f648974e4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java @@ -174,12 +174,11 @@ public Response updateGuestbook(@Context ContainerRequestContext crc, @Operation(summary = "Lists guestbook responses", description = "Returns guestbook metadata and response records, with pagination links when a limit is supplied.") public Response getResponses(@Context ContainerRequestContext crc, - @Parameter(description = "Numeric id of the guestbook whose responses are listed.", required = true) - @PathParam("id") Long id, - @Parameter(description = "Maximum number of response records to return.") - @QueryParam("limit") Integer limit, - @Parameter(description = "Response record offset.") - @QueryParam("offset") Integer offset) { + @Parameter(description = "Numeric id of the guestbook whose responses are listed.", required = true) @PathParam("id") Long id, + @Parameter(description = "Sort Field. One of: 'Dataset'; 'Date'; 'Type'; 'File'; 'User'") @QueryParam("sort") String sortField, + @Parameter(description = "Sort order. ('asc' or 'desc')") @QueryParam("order") String sortOrder, + @Parameter(description = "Maximum number of response records to return.") @QueryParam("limit") Integer limit, + @Parameter(description = "Response record offset.") @QueryParam("offset") Integer offset) { return response( req -> { Guestbook guestbook = guestbookService.find(id); @@ -195,7 +194,7 @@ public Response getResponses(@Context ContainerRequestContext crc, guestbook.setUsageCount(totalUsageCount); guestbook.setResponseCount(totalResponseCount); - List responses = guestbookResponseService.findAllByGuestbookId(guestbook.getId(), offset, limit); + List responses = guestbookResponseService.findAllByGuestbookId(guestbook.getId(), sortField, sortOrder, offset, limit); JsonObjectBuilder guestbookResponseObject = jsonObjectBuilder(); guestbookResponseObject.add("guestbook", JsonPrinter.json(guestbook)); 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 7bc06f7c92c..0737df7aa3d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4124,7 +4124,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars int totalCountFromJson = 0; for (int i = 0; i < pages; i++) { int offset = limit * i; - guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), offset, limit, ownerApiToken); + guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), "User", "asc", offset, limit, ownerApiToken); guestbookListResponses.prettyPrint(); jsonPath = JsonPath.from(guestbookListResponses.body().asString()); pagedTotalCount += jsonPath.getList("data.responses").size(); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java b/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java index e2483405270..c657202f543 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.Guestbook; import edu.harvard.iq.dataverse.util.json.JsonParseException; import edu.harvard.iq.dataverse.util.json.JsonParser; @@ -11,6 +12,8 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; +import java.util.Map; import static jakarta.ws.rs.core.Response.Status.OK; import static org.hamcrest.CoreMatchers.containsString; @@ -37,9 +40,9 @@ public void testGuestbook() throws IOException, JsonParseException { String guestbookAsJson = data.getJsonObject("data").toString(); // Test Update Guestbook - guestbookAsJson = guestbookAsJson.replace("my test guestbook","my modified test guestbook") - .replace("positionRequired\":false", "positionRequired\": true") - .replace("displayOrder\":3}", "displayOrder\":3},{\"value\":\"Green\",\"displayOrder\":4}"); // add a new option (Green) + guestbookAsJson = guestbookAsJson.replace("my test guestbook", "my modified test guestbook") + .replace("positionRequired\":false", "positionRequired\": true") + .replace("displayOrder\":3}", "displayOrder\":3},{\"value\":\"Green\",\"displayOrder\":4}"); // add a new option (Green) Response updateGuestbookResponse = UtilIT.updateGuestbook(guestbook.getId(), guestbookAsJson, apiToken); updateGuestbookResponse.then().assertThat().statusCode(OK.getStatusCode()); @@ -83,4 +86,11 @@ public void testGuestbook() throws IOException, JsonParseException { .statusCode(OK.getStatusCode()) .body("data.message", containsString("enabled=false")); } + + @Test + public void testSort2() { + String ownerApiToken = "02996486-52da-4b83-8b86-df4b8e9898e0"; + Response guestbookListResponses = UtilIT.getGuestbooksResponses(2L, "dataset", null, null, null, ownerApiToken); + guestbookListResponses.prettyPrint(); + } } 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 b6f8d66c0af..b1b3173faa5 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -641,6 +641,9 @@ static Response getGuestbooks(String dataverseAlias, String apiToken, boolean in } static Response getGuestbooksResponses(Long guestbookId, Integer offset, Integer limit, String apiToken) { + return getGuestbooksResponses(guestbookId, null, null, offset, limit, apiToken); + } + static Response getGuestbooksResponses(Long guestbookId, String sortField, String sortOrder, Integer offset, Integer limit, String apiToken) { RequestSpecification requestSpec = given(); if (apiToken != null) { requestSpec.header(API_TOKEN_HTTP_HEADER, apiToken); @@ -651,6 +654,12 @@ static Response getGuestbooksResponses(Long guestbookId, Integer offset, Integer if (limit != null) { requestSpec.queryParam("limit", limit); } + if (sortField != null) { + requestSpec.queryParam("sort", sortField); + } + if (sortOrder != null) { + requestSpec.queryParam("order", sortOrder); + } return requestSpec.get("/api/guestbooks/" + guestbookId + "/responses"); } From 38436ae78cc48e659dc601bc9a6f57b79c2e62a6 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:54:15 -0400 Subject: [PATCH 2/8] reset GuestbooksIT --- .../harvard/iq/dataverse/api/GuestbooksIT.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java b/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java index c657202f543..e2483405270 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/GuestbooksIT.java @@ -1,6 +1,5 @@ package edu.harvard.iq.dataverse.api; -import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.Guestbook; import edu.harvard.iq.dataverse.util.json.JsonParseException; import edu.harvard.iq.dataverse.util.json.JsonParser; @@ -12,8 +11,6 @@ import org.junit.jupiter.api.Test; import java.io.IOException; -import java.util.List; -import java.util.Map; import static jakarta.ws.rs.core.Response.Status.OK; import static org.hamcrest.CoreMatchers.containsString; @@ -40,9 +37,9 @@ public void testGuestbook() throws IOException, JsonParseException { String guestbookAsJson = data.getJsonObject("data").toString(); // Test Update Guestbook - guestbookAsJson = guestbookAsJson.replace("my test guestbook", "my modified test guestbook") - .replace("positionRequired\":false", "positionRequired\": true") - .replace("displayOrder\":3}", "displayOrder\":3},{\"value\":\"Green\",\"displayOrder\":4}"); // add a new option (Green) + guestbookAsJson = guestbookAsJson.replace("my test guestbook","my modified test guestbook") + .replace("positionRequired\":false", "positionRequired\": true") + .replace("displayOrder\":3}", "displayOrder\":3},{\"value\":\"Green\",\"displayOrder\":4}"); // add a new option (Green) Response updateGuestbookResponse = UtilIT.updateGuestbook(guestbook.getId(), guestbookAsJson, apiToken); updateGuestbookResponse.then().assertThat().statusCode(OK.getStatusCode()); @@ -86,11 +83,4 @@ public void testGuestbook() throws IOException, JsonParseException { .statusCode(OK.getStatusCode()) .body("data.message", containsString("enabled=false")); } - - @Test - public void testSort2() { - String ownerApiToken = "02996486-52da-4b83-8b86-df4b8e9898e0"; - Response guestbookListResponses = UtilIT.getGuestbooksResponses(2L, "dataset", null, null, null, ownerApiToken); - guestbookListResponses.prettyPrint(); - } } From 5c7dceac8addfcad0d9c7a184a4d951fac0320e3 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:32:09 -0400 Subject: [PATCH 3/8] working on sort code. 'dataset' still needs fixing --- .../GuestbookResponseServiceBean.java | 19 ++++++++---- .../edu/harvard/iq/dataverse/api/FilesIT.java | 29 ++++++++++++++++++- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 01e7859f53a..9321cb41e65 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -126,13 +126,17 @@ public List findAllByGuestbookId(Long guestbookId, String sor String orderField = (sortField == null) ? "" : sortField.toLowerCase(); switch(orderField) { case "dataset": - Join datasetVersionJoin = guestbookResponseRoot.join("datasetVersion", JoinType.INNER); - Join datasetFieldJoin = datasetVersionJoin.join("datasetFields", JoinType.INNER); + Join datasetJoin = guestbookResponseRoot.join("dataset", JoinType.INNER); + ListJoin datasetVersionJoin = datasetJoin.joinList("versions", JoinType.INNER); + ListJoin datasetFieldJoin = datasetVersionJoin.joinList("datasetFields", JoinType.INNER); Join datasetFieldTypeJoin = datasetFieldJoin.join("datasetFieldType", JoinType.INNER); - Join datasetFieldValueJoin = datasetFieldJoin.join("datasetFieldValues", JoinType.INNER); + ListJoin datasetFieldValueJoin = datasetFieldJoin.joinList("datasetFieldValues", JoinType.INNER); datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), 1)); // 1 -> title TODO get this instead of hard coding it order = getOrderBy(cb, datasetFieldValueJoin.get("value"), isDescending); break; + case "date": + order = getOrderBy(cb, guestbookResponseRoot.get("responseTime"), isDescending); + break; case "type": order = getOrderBy(cb, guestbookResponseRoot.get("eventType"), isDescending); break; @@ -144,14 +148,17 @@ public List findAllByGuestbookId(Long guestbookId, String sor order = getOrderBy(cb, guestbookResponseRoot.get("name"), isDescending); break; default: - order = getOrderBy(cb, guestbookResponseRoot.get("responseTime"), isDescending); + order = null; } cq.where(cb.equal(guestbookResponseRoot.get("guestbook").get("id"), guestbookId)); - cq.orderBy(order); + if (order != null) { + cq.orderBy(order); + } + cq.distinct(true); int firstResult = offset == null ? 0 : offset; - int pageSize = limit == null ? 10 : limit; + int pageSize = limit == null ? Integer.MAX_VALUE : limit; List result = em.createQuery(cq) .setFirstResult(firstResult) 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 0737df7aa3d..45da611d812 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4117,6 +4117,12 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars assertTrue(totalCount > 0); assertNotNull(jsonPath.getString("data.responses[0].name")); + // Test Get All Responses Sorted + testSortByField(guestbook.getId(), "file", "asc", ownerApiToken); + testSortByField(guestbook.getId(), "user", "asc", ownerApiToken); + testSortByField(guestbook.getId(), "user", "desc", ownerApiToken); + testSortByField(guestbook.getId(), "dataset", null, ownerApiToken); + // 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 @@ -4124,7 +4130,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars int totalCountFromJson = 0; for (int i = 0; i < pages; i++) { int offset = limit * i; - guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), "User", "asc", offset, limit, ownerApiToken); + guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), "date", null, offset, limit, ownerApiToken); guestbookListResponses.prettyPrint(); jsonPath = JsonPath.from(guestbookListResponses.body().asString()); pagedTotalCount += jsonPath.getList("data.responses").size(); @@ -4136,6 +4142,27 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars assertEquals(pagedTotalCount, totalCountFromJson); } + private void testSortByField(Long id, String sortField, String order, String token) { + Response guestbookListResponses = UtilIT.getGuestbooksResponses(id, sortField, order, 0, Integer.MAX_VALUE, token); + Map fieldMap = Map.of("dataset", "dataset", "type", "type", "user", "name", "file", "fileName", "date", "date"); + String fieldName = fieldMap.get(sortField); + boolean isDescending = order != null && order.equalsIgnoreCase("desc"); + guestbookListResponses.prettyPrint(); + guestbookListResponses.then().assertThat() + .statusCode(OK.getStatusCode()); + JsonPath jsonPath = JsonPath.from(guestbookListResponses.body().asString()); + int totalCount = jsonPath.getList("data.responses").size(); + assertTrue(totalCount > 0); + String lastFieldValue = jsonPath.getString("data.responses[0]." + fieldName).toLowerCase(); // The sort seems to be case-insensitive + for (int i = 1; i < totalCount; i++) { + String fieldValue = jsonPath.getString("data.responses[" + i + "]." + fieldName).toLowerCase(); + System.out.println("lastFieldValue:" + lastFieldValue + " fieldValue:" + fieldValue + " " + fieldValue.compareTo(lastFieldValue)); + assertTrue(isDescending ? fieldValue.compareTo(lastFieldValue) <= 0 : fieldValue.compareTo(lastFieldValue) >= 0); + lastFieldValue = fieldValue; + } + System.out.println("total rows: " + totalCount); + } + @Test public void testGetFileCitationFormatted() { Response createUser = UtilIT.createRandomUser(); From 7ff8f6b18fcbd5a67690dd48248eb212466140bb Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:09:52 -0400 Subject: [PATCH 4/8] fix dataset title sort --- .../harvard/iq/dataverse/GuestbookResponse.java | 16 ++++++++++++++-- .../dataverse/GuestbookResponseServiceBean.java | 7 +++---- src/main/resources/db/migration/V6.11.0.1.sql | 17 +++++++++++++++++ .../edu/harvard/iq/dataverse/api/FilesIT.java | 5 +++-- 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/db/migration/V6.11.0.1.sql diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java index a6ac270b45c..943b0c5eb76 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java @@ -173,8 +173,8 @@ public GuestbookResponse(GuestbookResponse source){ this.setInstitution(source.getInstitution()); this.setPosition(source.getPosition()); this.setResponseTime(source.getResponseTime()); - this.setDataset(source.getDataset()); this.setDatasetVersion(source.getDatasetVersion()); + this.setDataset(source.getDataset()); // will default DatasetVersion if null this.setAuthenticatedUser(source.getAuthenticatedUser()); this.setSessionId(source.getSessionId()); List customQuestionResponses = new ArrayList<>(); @@ -287,6 +287,18 @@ public Dataset getDataset() { public void setDataset(Dataset dataset) { this.dataset = dataset; + // If not set the DatasetVersion will default + setDatasetVersion(getDefaultDatasetVersion(getDatasetVersion())); + } + + private DatasetVersion getDefaultDatasetVersion(DatasetVersion version) { + if (version == null && dataset != null) { + version = dataset.getReleasedVersion(); + if (version == null) { + version = dataset.getLatestVersion(); + } + } + return version; } public DataFile getDataFile() { @@ -302,7 +314,7 @@ public DatasetVersion getDatasetVersion() { } public void setDatasetVersion(DatasetVersion datasetVersion) { - this.datasetVersion = datasetVersion; + this.datasetVersion = getDefaultDatasetVersion(datasetVersion); } public AuthenticatedUser getAuthenticatedUser() { diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 9321cb41e65..a1f8cf8621b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -126,12 +126,11 @@ public List findAllByGuestbookId(Long guestbookId, String sor String orderField = (sortField == null) ? "" : sortField.toLowerCase(); switch(orderField) { case "dataset": - Join datasetJoin = guestbookResponseRoot.join("dataset", JoinType.INNER); - ListJoin datasetVersionJoin = datasetJoin.joinList("versions", JoinType.INNER); - ListJoin datasetFieldJoin = datasetVersionJoin.joinList("datasetFields", JoinType.INNER); + Join datasetVersionJoin = guestbookResponseRoot.join("datasetVersion", JoinType.INNER); + Join datasetFieldJoin = datasetVersionJoin.join("datasetFields", JoinType.INNER); Join datasetFieldTypeJoin = datasetFieldJoin.join("datasetFieldType", JoinType.INNER); - ListJoin datasetFieldValueJoin = datasetFieldJoin.joinList("datasetFieldValues", JoinType.INNER); datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), 1)); // 1 -> title TODO get this instead of hard coding it + Join datasetFieldValueJoin = datasetFieldJoin.join("datasetFieldValues", JoinType.INNER); order = getOrderBy(cb, datasetFieldValueJoin.get("value"), isDescending); break; case "date": diff --git a/src/main/resources/db/migration/V6.11.0.1.sql b/src/main/resources/db/migration/V6.11.0.1.sql new file mode 100644 index 00000000000..497a22c9909 --- /dev/null +++ b/src/main/resources/db/migration/V6.11.0.1.sql @@ -0,0 +1,17 @@ +-- Update all guestbook responses with missing dataset versions to default to dataset's latest version +UPDATE guestbookresponse as gr +SET datasetversion_id = subquery.dsvid +FROM ( + select g.id gid, dsv.id dsvid + from guestbookresponse g + inner join dataset ds on g.dataset_id = ds.id + INNER JOIN LATERAL ( + SELECT id + FROM datasetversion dsv + WHERE dsv.dataset_id = ds.id + ORDER BY dsv.id DESC + LIMIT 1 + ) dsv ON true + where g.datasetversion_id is null +) AS subquery +WHERE gr.id = subquery.gid; 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 45da611d812..3d45c42c510 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4156,11 +4156,12 @@ private void testSortByField(Long id, String sortField, String order, String tok String lastFieldValue = jsonPath.getString("data.responses[0]." + fieldName).toLowerCase(); // The sort seems to be case-insensitive for (int i = 1; i < totalCount; i++) { String fieldValue = jsonPath.getString("data.responses[" + i + "]." + fieldName).toLowerCase(); - System.out.println("lastFieldValue:" + lastFieldValue + " fieldValue:" + fieldValue + " " + fieldValue.compareTo(lastFieldValue)); + //System.out.println("lastFieldValue:" + lastFieldValue + " fieldValue:" + fieldValue + " " + fieldValue.compareTo(lastFieldValue)); assertTrue(isDescending ? fieldValue.compareTo(lastFieldValue) <= 0 : fieldValue.compareTo(lastFieldValue) >= 0); lastFieldValue = fieldValue; } - System.out.println("total rows: " + totalCount); + int totalCountFromJson = jsonPath.getInt("data.pagination.totalResponses"); + assertEquals(totalCountFromJson, totalCount); } @Test From 28f4f21655f6ee01dc7b14aa9573c25c4fc520ea Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:47:16 -0400 Subject: [PATCH 5/8] remove hard coded title field id --- .../harvard/iq/dataverse/DatasetFieldConstant.java | 2 +- .../iq/dataverse/GuestbookResponseServiceBean.java | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java index 71e339a6fca..4d3ebfe9c59 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java @@ -178,7 +178,7 @@ public class DatasetFieldConstant implements java.io.Serializable { * The following getters are needed so we can use them as properties in JSP */ - public String getTitle() { + public static String getTitle() { return title; } diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index a1f8cf8621b..2fefc001ed9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -40,6 +40,8 @@ public class GuestbookResponseServiceBean { @EJB DataverseServiceBean dataverseService; + @EJB + DatasetFieldServiceBean datasetFieldService; // The query below is used for retrieving guestbook responses used to download // the collected data, in CSV format, from the manage-guestbooks and @@ -97,6 +99,8 @@ public class GuestbookResponseServiceBean { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; + private static Long datasetFieldTypeTitleId = null; + public List findAll() { return em.createQuery("select object(o) from GuestbookResponse as o order by o.responseTime desc", GuestbookResponse.class).getResultList(); } @@ -112,6 +116,14 @@ public List findAllIds(Long dataverseId) { return em.createQuery("select o.id from GuestbookResponse o, Dataset d where o.dataset.id = d.id and d.owner.id = " + dataverseId + " order by o.responseTime desc", Long.class).getResultList(); } + private Long getTitleFieldTypeId() { + if (datasetFieldTypeTitleId == null) { + DatasetFieldType type = datasetFieldService.findByName(DatasetFieldConstant.getTitle()); + datasetFieldTypeTitleId = type != null ? type.getId() : 1L; + } + return datasetFieldTypeTitleId; + } + private Order getOrderBy(CriteriaBuilder cb, Path pathObj, boolean isDescending) { return isDescending ? cb.desc(pathObj) : cb.asc(pathObj); } @@ -129,7 +141,7 @@ public List findAllByGuestbookId(Long guestbookId, String sor Join datasetVersionJoin = guestbookResponseRoot.join("datasetVersion", JoinType.INNER); Join datasetFieldJoin = datasetVersionJoin.join("datasetFields", JoinType.INNER); Join datasetFieldTypeJoin = datasetFieldJoin.join("datasetFieldType", JoinType.INNER); - datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), 1)); // 1 -> title TODO get this instead of hard coding it + datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), getTitleFieldTypeId())); Join datasetFieldValueJoin = datasetFieldJoin.join("datasetFieldValues", JoinType.INNER); order = getOrderBy(cb, datasetFieldValueJoin.get("value"), isDescending); break; From 605ab31ca396b5086a1aa0625e93b642ed03c71b Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:36:03 -0400 Subject: [PATCH 6/8] modify sql script --- .../12524-sort-guestbook-responses.md | 8 +++- src/main/resources/db/migration/V6.11.0.1.sql | 46 ++++++++++++------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/doc/release-notes/12524-sort-guestbook-responses.md b/doc/release-notes/12524-sort-guestbook-responses.md index aca1bd9e08c..33274971c73 100644 --- a/doc/release-notes/12524-sort-guestbook-responses.md +++ b/doc/release-notes/12524-sort-guestbook-responses.md @@ -1,2 +1,8 @@ ## Feature ## -Added the ability to sort the Guestbook Responses in API /api/guestbooks/{id}/responses by Dataset Name, Event Type, File Name, User Name, and Response Date +This feature adds the ability to sort the Guestbook Responses in the API /api/guestbooks/{id}/responses. Responses can be sorted by Dataset Name(dataset), Event Type(type), File Name(file), User Name(user), and Response Date(date) + +### Note: ### +Part of this feature is the requirement that all Guestbook Responses have a pointer to the Dataset Version. Null Dataset Versions will prevent the Guestbook response from showing in the list when sorting by Dataset Name. This script runs automatically when upgrading Dataverse. The SQL script can take a considerable amount of time to run. It is advised to run the script prior to doing the upgrade. There is no issue running the script on earlier versions of Dataverse or running the script multiple times. + +### PSQL script: ### +``psql -U {dbUser} -d {database} -c 'DO $$ DECLARE batch_size INT := 10000; rows_affected INT; BEGIN LOOP WITH batch AS (select g.id gid, dsv.id dsvid from guestbookresponse g inner join dataset ds on g.dataset_id = ds.id INNER JOIN LATERAL (SELECT id FROM datasetversion dsv WHERE dsv.dataset_id = ds.id ORDER BY dsv.id DESC LIMIT 1 ) dsv ON true where g.datasetversion_id is null LIMIT batch_size FOR UPDATE) UPDATE guestbookresponse gb SET datasetversion_id = dsvid FROM batch WHERE gb.id = batch.gid; GET DIAGNOSTICS rows_affected = ROW_COUNT;EXIT WHEN rows_affected = 0;END LOOP;END $$;' `` diff --git a/src/main/resources/db/migration/V6.11.0.1.sql b/src/main/resources/db/migration/V6.11.0.1.sql index 497a22c9909..c3519a656ac 100644 --- a/src/main/resources/db/migration/V6.11.0.1.sql +++ b/src/main/resources/db/migration/V6.11.0.1.sql @@ -1,17 +1,31 @@ -- Update all guestbook responses with missing dataset versions to default to dataset's latest version -UPDATE guestbookresponse as gr -SET datasetversion_id = subquery.dsvid -FROM ( - select g.id gid, dsv.id dsvid - from guestbookresponse g - inner join dataset ds on g.dataset_id = ds.id - INNER JOIN LATERAL ( - SELECT id - FROM datasetversion dsv - WHERE dsv.dataset_id = ds.id - ORDER BY dsv.id DESC - LIMIT 1 - ) dsv ON true - where g.datasetversion_id is null -) AS subquery -WHERE gr.id = subquery.gid; +DO $$ +DECLARE +batch_size INT := 10000; + rows_affected INT; +BEGIN + LOOP + WITH batch AS ( + select g.id gid, dsv.id dsvid + from guestbookresponse g + inner join dataset ds on g.dataset_id = ds.id + INNER JOIN LATERAL ( + SELECT id + FROM datasetversion dsv + WHERE dsv.dataset_id = ds.id + ORDER BY dsv.id DESC + LIMIT 1 + ) dsv ON true + where g.datasetversion_id is null + LIMIT batch_size + FOR UPDATE + ) + UPDATE guestbookresponse gb + SET datasetversion_id = dsvid + FROM batch + WHERE gb.id = batch.gid; + + GET DIAGNOSTICS rows_affected = ROW_COUNT; + EXIT WHEN rows_affected = 0; + END LOOP; +END $$; From dd1cb81f83127dc209b982f15f36a033587d9662 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:50:07 -0400 Subject: [PATCH 7/8] remove sort by Dataset Title --- .../12524-sort-guestbook-responses.md | 8 +---- doc/sphinx-guides/source/api/native-api.rst | 3 +- .../iq/dataverse/DatasetFieldConstant.java | 2 +- .../iq/dataverse/GuestbookResponse.java | 16 ++-------- .../GuestbookResponseServiceBean.java | 18 ----------- src/main/resources/db/migration/V6.11.0.1.sql | 31 ------------------- .../edu/harvard/iq/dataverse/api/FilesIT.java | 5 +-- 7 files changed, 8 insertions(+), 75 deletions(-) delete mode 100644 src/main/resources/db/migration/V6.11.0.1.sql diff --git a/doc/release-notes/12524-sort-guestbook-responses.md b/doc/release-notes/12524-sort-guestbook-responses.md index 33274971c73..6c2a402436d 100644 --- a/doc/release-notes/12524-sort-guestbook-responses.md +++ b/doc/release-notes/12524-sort-guestbook-responses.md @@ -1,8 +1,2 @@ ## Feature ## -This feature adds the ability to sort the Guestbook Responses in the API /api/guestbooks/{id}/responses. Responses can be sorted by Dataset Name(dataset), Event Type(type), File Name(file), User Name(user), and Response Date(date) - -### Note: ### -Part of this feature is the requirement that all Guestbook Responses have a pointer to the Dataset Version. Null Dataset Versions will prevent the Guestbook response from showing in the list when sorting by Dataset Name. This script runs automatically when upgrading Dataverse. The SQL script can take a considerable amount of time to run. It is advised to run the script prior to doing the upgrade. There is no issue running the script on earlier versions of Dataverse or running the script multiple times. - -### PSQL script: ### -``psql -U {dbUser} -d {database} -c 'DO $$ DECLARE batch_size INT := 10000; rows_affected INT; BEGIN LOOP WITH batch AS (select g.id gid, dsv.id dsvid from guestbookresponse g inner join dataset ds on g.dataset_id = ds.id INNER JOIN LATERAL (SELECT id FROM datasetversion dsv WHERE dsv.dataset_id = ds.id ORDER BY dsv.id DESC LIMIT 1 ) dsv ON true where g.datasetversion_id is null LIMIT batch_size FOR UPDATE) UPDATE guestbookresponse gb SET datasetversion_id = dsvid FROM batch WHERE gb.id = batch.gid; GET DIAGNOSTICS rows_affected = ROW_COUNT;EXIT WHEN rows_affected = 0;END LOOP;END $$;' `` +This feature adds the ability to sort the Guestbook Responses in the API /api/guestbooks/{id}/responses. Responses can be sorted by Event Type(type), File Name(file), User Name(user), and Response Date(date) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 859071f7448..076185c4540 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1445,7 +1445,6 @@ 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 Responses can be sorted by specifying one of the following in query parameter 'sort' and 'order': -* ``dataset``: Dataset Name * ``type``: Event Type * ``file``: File Name * ``user``: User Name @@ -1464,7 +1463,7 @@ The resulting Json will be more detailed than that of the :ref:`download-guestbo 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&sort=dataset&order=asc" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0&sort=file&order=asc" The fully expanded example above (without environment variables) looks like this: diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java index 4d3ebfe9c59..71e339a6fca 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldConstant.java @@ -178,7 +178,7 @@ public class DatasetFieldConstant implements java.io.Serializable { * The following getters are needed so we can use them as properties in JSP */ - public static String getTitle() { + public String getTitle() { return title; } diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java index 943b0c5eb76..a6ac270b45c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java @@ -173,8 +173,8 @@ public GuestbookResponse(GuestbookResponse source){ this.setInstitution(source.getInstitution()); this.setPosition(source.getPosition()); this.setResponseTime(source.getResponseTime()); + this.setDataset(source.getDataset()); this.setDatasetVersion(source.getDatasetVersion()); - this.setDataset(source.getDataset()); // will default DatasetVersion if null this.setAuthenticatedUser(source.getAuthenticatedUser()); this.setSessionId(source.getSessionId()); List customQuestionResponses = new ArrayList<>(); @@ -287,18 +287,6 @@ public Dataset getDataset() { public void setDataset(Dataset dataset) { this.dataset = dataset; - // If not set the DatasetVersion will default - setDatasetVersion(getDefaultDatasetVersion(getDatasetVersion())); - } - - private DatasetVersion getDefaultDatasetVersion(DatasetVersion version) { - if (version == null && dataset != null) { - version = dataset.getReleasedVersion(); - if (version == null) { - version = dataset.getLatestVersion(); - } - } - return version; } public DataFile getDataFile() { @@ -314,7 +302,7 @@ public DatasetVersion getDatasetVersion() { } public void setDatasetVersion(DatasetVersion datasetVersion) { - this.datasetVersion = getDefaultDatasetVersion(datasetVersion); + this.datasetVersion = datasetVersion; } public AuthenticatedUser getAuthenticatedUser() { diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 2fefc001ed9..6d0256396f8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -40,8 +40,6 @@ public class GuestbookResponseServiceBean { @EJB DataverseServiceBean dataverseService; - @EJB - DatasetFieldServiceBean datasetFieldService; // The query below is used for retrieving guestbook responses used to download // the collected data, in CSV format, from the manage-guestbooks and @@ -116,14 +114,6 @@ public List findAllIds(Long dataverseId) { return em.createQuery("select o.id from GuestbookResponse o, Dataset d where o.dataset.id = d.id and d.owner.id = " + dataverseId + " order by o.responseTime desc", Long.class).getResultList(); } - private Long getTitleFieldTypeId() { - if (datasetFieldTypeTitleId == null) { - DatasetFieldType type = datasetFieldService.findByName(DatasetFieldConstant.getTitle()); - datasetFieldTypeTitleId = type != null ? type.getId() : 1L; - } - return datasetFieldTypeTitleId; - } - private Order getOrderBy(CriteriaBuilder cb, Path pathObj, boolean isDescending) { return isDescending ? cb.desc(pathObj) : cb.asc(pathObj); } @@ -137,14 +127,6 @@ public List findAllByGuestbookId(Long guestbookId, String sor Order order; String orderField = (sortField == null) ? "" : sortField.toLowerCase(); switch(orderField) { - case "dataset": - Join datasetVersionJoin = guestbookResponseRoot.join("datasetVersion", JoinType.INNER); - Join datasetFieldJoin = datasetVersionJoin.join("datasetFields", JoinType.INNER); - Join datasetFieldTypeJoin = datasetFieldJoin.join("datasetFieldType", JoinType.INNER); - datasetFieldTypeJoin.on(cb.equal(datasetFieldTypeJoin.get("id"), getTitleFieldTypeId())); - Join datasetFieldValueJoin = datasetFieldJoin.join("datasetFieldValues", JoinType.INNER); - order = getOrderBy(cb, datasetFieldValueJoin.get("value"), isDescending); - break; case "date": order = getOrderBy(cb, guestbookResponseRoot.get("responseTime"), isDescending); break; diff --git a/src/main/resources/db/migration/V6.11.0.1.sql b/src/main/resources/db/migration/V6.11.0.1.sql deleted file mode 100644 index c3519a656ac..00000000000 --- a/src/main/resources/db/migration/V6.11.0.1.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Update all guestbook responses with missing dataset versions to default to dataset's latest version -DO $$ -DECLARE -batch_size INT := 10000; - rows_affected INT; -BEGIN - LOOP - WITH batch AS ( - select g.id gid, dsv.id dsvid - from guestbookresponse g - inner join dataset ds on g.dataset_id = ds.id - INNER JOIN LATERAL ( - SELECT id - FROM datasetversion dsv - WHERE dsv.dataset_id = ds.id - ORDER BY dsv.id DESC - LIMIT 1 - ) dsv ON true - where g.datasetversion_id is null - LIMIT batch_size - FOR UPDATE - ) - UPDATE guestbookresponse gb - SET datasetversion_id = dsvid - FROM batch - WHERE gb.id = batch.gid; - - GET DIAGNOSTICS rows_affected = ROW_COUNT; - EXIT WHEN rows_affected = 0; - END LOOP; -END $$; 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 3d45c42c510..4b84e76d996 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4121,7 +4121,8 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars testSortByField(guestbook.getId(), "file", "asc", ownerApiToken); testSortByField(guestbook.getId(), "user", "asc", ownerApiToken); testSortByField(guestbook.getId(), "user", "desc", ownerApiToken); - testSortByField(guestbook.getId(), "dataset", null, ownerApiToken); + testSortByField(guestbook.getId(), "date", "asc", ownerApiToken); + testSortByField(guestbook.getId(), "date", "desc", ownerApiToken); // Test Get Responses with pagination int pages = 4; // total should be 17. set to 4 pages @@ -4144,7 +4145,7 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars private void testSortByField(Long id, String sortField, String order, String token) { Response guestbookListResponses = UtilIT.getGuestbooksResponses(id, sortField, order, 0, Integer.MAX_VALUE, token); - Map fieldMap = Map.of("dataset", "dataset", "type", "type", "user", "name", "file", "fileName", "date", "date"); + Map fieldMap = Map.of("type", "type", "user", "name", "file", "fileName", "date", "date"); String fieldName = fieldMap.get(sortField); boolean isDescending = order != null && order.equalsIgnoreCase("desc"); guestbookListResponses.prettyPrint(); From 4d9267e694a28e3490963911962b7afe01557a05 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:56:03 -0400 Subject: [PATCH 8/8] fixes from review comments --- doc/sphinx-guides/source/api/native-api.rst | 2 +- .../harvard/iq/dataverse/GuestbookResponseServiceBean.java | 6 +----- src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 076185c4540..e68471b1a0e 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1470,7 +1470,7 @@ 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&sort=type&order=desc + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1/responses?limit10&offset=0&sort=type&order=desc" .. _collection-attributes-api: diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 6d0256396f8..9c764acd065 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -97,8 +97,6 @@ public class GuestbookResponseServiceBean { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; - private static Long datasetFieldTypeTitleId = null; - public List findAll() { return em.createQuery("select object(o) from GuestbookResponse as o order by o.responseTime desc", GuestbookResponse.class).getResultList(); } @@ -153,12 +151,10 @@ public List findAllByGuestbookId(Long guestbookId, String sor int firstResult = offset == null ? 0 : offset; int pageSize = limit == null ? Integer.MAX_VALUE : limit; - List result = em.createQuery(cq) + return em.createQuery(cq) .setFirstResult(firstResult) .setMaxResults(pageSize) .getResultList(); - - return result; } return null; } 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 4b84e76d996..47f3d704c71 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4157,7 +4157,6 @@ private void testSortByField(Long id, String sortField, String order, String tok String lastFieldValue = jsonPath.getString("data.responses[0]." + fieldName).toLowerCase(); // The sort seems to be case-insensitive for (int i = 1; i < totalCount; i++) { String fieldValue = jsonPath.getString("data.responses[" + i + "]." + fieldName).toLowerCase(); - //System.out.println("lastFieldValue:" + lastFieldValue + " fieldValue:" + fieldValue + " " + fieldValue.compareTo(lastFieldValue)); assertTrue(isDescending ? fieldValue.compareTo(lastFieldValue) <= 0 : fieldValue.compareTo(lastFieldValue) >= 0); lastFieldValue = fieldValue; }