Skip to content

Commit dfd0198

Browse files
authored
Merge pull request #12395 from IQSS/12386-manage-guestbook-apis
Manage Guestbook APIs to Edit Guestbooks and retrieve Guestbook Respo…
2 parents a3b8fd7 + bc94b4c commit dfd0198

10 files changed

Lines changed: 342 additions & 15 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Feature - Manage Guestbook
2+
3+
This feature adds 2 new APIs to help manage Guestbooks and Guestbook Responses.
4+
5+
6+
This API allows the user to make edits to an existing Guestbook, including adding and removing Custom Guestbook Questions.
7+
8+
`curl -PUT -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}" -d "$JSON"`
9+
10+
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.
11+
12+
`curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0"`

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,28 @@ The fully expanded example above (without environment variables) looks like this
12751275
12761276
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1234"
12771277
1278+
Update a Guestbook for a Dataverse Collection
1279+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1280+
1281+
For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.
1282+
1283+
Update a Guestbook that can be selected for a Dataset.
1284+
You must have "EditDataverse" permission on the Dataverse collection.
1285+
1286+
.. code-block:: bash
1287+
1288+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
1289+
export SERVER_URL=https://demo.dataverse.org
1290+
export ID=1234
1291+
1292+
curl -PUT -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/{ID}" -d "$JSON"
1293+
1294+
The fully expanded example above (without environment variables) looks like this:
1295+
1296+
.. code-block:: bash
1297+
1298+
curl -PUT -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1234" -d "$JSON"
1299+
12781300
Enable or Disable a Guestbook for a Dataverse Collection
12791301
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12801302

@@ -1298,6 +1320,32 @@ The fully expanded example above (without environment variables) looks like this
12981320
12991321
curl -X PUT -d 'true' -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/root/1234"
13001322
1323+
Retrieve Guestbook Responses for a Guestbook
1324+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1325+
1326+
For more about guestbooks, see :ref:`dataset-guestbooks` in the User Guide.
1327+
1328+
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.
1329+
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.
1330+
1331+
.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of ``export`` below.
1332+
1333+
.. code-block:: bash
1334+
1335+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
1336+
export SERVER_URL=https://demo.dataverse.org
1337+
export ID=1
1338+
1339+
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses"
1340+
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/guestbooks/$ID/responses?limit10&offset=0"
1341+
1342+
The fully expanded example above (without environment variables) looks like this:
1343+
1344+
.. code-block:: bash
1345+
1346+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1/responses"
1347+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/guestbooks/1/responses?limit10&offset=0"
1348+
13011349
.. _collection-attributes-api:
13021350

13031351
Change Collection Attributes

src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,19 @@ public List<Long> findAllIds(Long dataverseId) {
111111
}
112112

113113
public List<GuestbookResponse> findAllByGuestbookId(Long guestbookId) {
114+
return findAllByGuestbookId(guestbookId, null, null);
115+
}
116+
public List<GuestbookResponse> findAllByGuestbookId(Long guestbookId, Integer offset, Integer limit) {
117+
if (guestbookId != null) {
118+
TypedQuery<GuestbookResponse> query = em.createQuery("select o from GuestbookResponse as o where o.guestbook.id = " + guestbookId + " order by o.responseTime desc", GuestbookResponse.class);
119+
if (offset != null) {
120+
query.setFirstResult(offset);
121+
}
122+
if (limit != null) {
123+
query.setMaxResults(limit);
124+
}
114125

115-
if (guestbookId == null) {
116-
} else {
117-
return em.createQuery("select o from GuestbookResponse as o where o.guestbook.id = " + guestbookId + " order by o.responseTime desc", GuestbookResponse.class).getResultList();
126+
return query.getResultList();
118127
}
119128
return null;
120129
}

src/main/java/edu/harvard/iq/dataverse/api/Guestbooks.java

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package edu.harvard.iq.dataverse.api;
22

3-
import edu.harvard.iq.dataverse.Dataverse;
4-
import edu.harvard.iq.dataverse.Guestbook;
5-
import edu.harvard.iq.dataverse.GuestbookResponseServiceBean;
6-
import edu.harvard.iq.dataverse.GuestbookServiceBean;
3+
import edu.harvard.iq.dataverse.*;
74
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
85
import edu.harvard.iq.dataverse.authorization.Permission;
96
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
@@ -31,6 +28,7 @@
3128
import java.util.logging.Logger;
3229

3330
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
31+
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
3432

3533
@Path("guestbooks")
3634
public class Guestbooks extends AbstractApiBean {
@@ -119,6 +117,80 @@ public Response createGuestbook(@Context ContainerRequestContext crc, @PathParam
119117
}
120118
}
121119

120+
@PUT
121+
@AuthRequired
122+
@Path("{id}")
123+
public Response updateGuestbook(@Context ContainerRequestContext crc, @PathParam("id") Long id, String jsonBody) {
124+
return response( req -> {
125+
Guestbook guestbook = guestbookService.find(id);
126+
if (guestbook != null) {
127+
try {
128+
JsonObject jsonObj = JsonUtil.getJsonObject(jsonBody);
129+
jsonParser().parseGuestbook(jsonObj, guestbook);
130+
} catch (JsonException | JsonParseException ex) {
131+
logger.log(Level.WARNING, "Error parsing guestbook JSON", ex);
132+
return badRequest("Error parsing guestbook JSON");
133+
}
134+
Guestbook newGuestbook = execCommand(new UpdateGuestbookCommand(guestbook, req, guestbook.getDataverse()));
135+
return ok(json(newGuestbook));
136+
} else {
137+
return notFound(BundleUtil.getStringFromBundle("dataset.manageGuestbooks.message.notFound"));
138+
}
139+
}, getRequestUser(crc));
140+
}
141+
142+
@GET
143+
@AuthRequired
144+
@Path("/{id}/responses")
145+
public Response getResponses(@Context ContainerRequestContext crc, @PathParam("id") Long id,
146+
@QueryParam("limit") Integer limit, @QueryParam("offset") Integer offset) {
147+
148+
return response( req -> {
149+
Guestbook guestbook = guestbookService.find(id);
150+
if (guestbook == null) {
151+
return notFound("Guestbook " + id + " not found.");
152+
}
153+
Dataverse dataverse = guestbook.getDataverse();
154+
if (!permissionSvc.request(req).on(dataverse).has(Permission.EditDataverse)) {
155+
return error(Response.Status.FORBIDDEN, "Not authorized");
156+
}
157+
Long totalUsageCount = guestbookService.findCountUsages(guestbook.getId(), dataverse.getId());
158+
Long totalResponseCount = guestbookResponseService.findCountByGuestbookId(guestbook.getId(), dataverse.getId());
159+
guestbook.setUsageCount(totalUsageCount);
160+
guestbook.setResponseCount(totalResponseCount);
161+
162+
List<GuestbookResponse> responses = guestbookResponseService.findAllByGuestbookId(guestbook.getId(), offset, limit);
163+
164+
JsonObjectBuilder guestbookResponseObject = jsonObjectBuilder();
165+
guestbookResponseObject.add("guestbook", JsonPrinter.json(guestbook));
166+
167+
JsonArrayBuilder responseObjects = Json.createArrayBuilder();
168+
for (GuestbookResponse gr : responses) {
169+
responseObjects.add(JsonPrinter.json(gr));
170+
}
171+
guestbookResponseObject.add("responses", responseObjects);
172+
173+
if (limit != null) {
174+
JsonObjectBuilder guestbookPageObject = jsonObjectBuilder();
175+
int thisOffset = offset != null ? offset : 0;
176+
int next = thisOffset + limit;
177+
int prev = thisOffset - limit;
178+
179+
String baseUrl = crc.getUriInfo().getAbsolutePath() + "?limit=" + limit + "&offset=" ;
180+
if (prev >= 0) {
181+
guestbookPageObject.add("previous",baseUrl + prev);
182+
}
183+
if (next < totalResponseCount) {
184+
guestbookPageObject.add("next", baseUrl + next);
185+
}
186+
guestbookPageObject.add("totalResponses", totalResponseCount);
187+
188+
guestbookResponseObject.add("pagination", guestbookPageObject);
189+
}
190+
return ok(guestbookResponseObject);
191+
}, getRequestUser(crc));
192+
}
193+
122194
@PUT
123195
@AuthRequired
124196
@Path("{identifier}/{id}/enabled")

src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,9 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th
553553

554554
public Guestbook parseGuestbook(JsonObject obj, Guestbook gb) throws JsonParseException {
555555
try {
556+
if (obj.containsKey("id")) {
557+
gb.setId(Long.valueOf(obj.getInt("id")));
558+
}
556559
gb.setName(obj.getString("name", null));
557560
gb.setEnabled(obj.getBoolean("enabled"));
558561
gb.setEmailRequired(obj.getBoolean("emailRequired"));
@@ -573,6 +576,9 @@ private List<CustomQuestion> parseCustomQuestions(JsonArray customQuestions, Gue
573576
customQuestions.forEach(q -> {
574577
JsonObject obj = q.asJsonObject();
575578
CustomQuestion cq = new CustomQuestion();
579+
if (obj.containsKey("id")) {
580+
cq.setId(Long.valueOf(obj.getInt("id")));
581+
}
576582
cq.setQuestionString(obj.getString("question"));
577583
cq.setRequired(obj.getBoolean("required"));
578584
cq.setDisplayOrder(obj.getInt("displayOrder"));
@@ -586,6 +592,9 @@ private List<CustomQuestion> parseCustomQuestions(JsonArray customQuestions, Gue
586592
optionValues.forEach(v -> {
587593
JsonObject ov = v.asJsonObject();
588594
CustomQuestionValue cqv = new CustomQuestionValue();
595+
if (ov.containsKey("id")) {
596+
cqv.setId(Long.valueOf(ov.getInt("id")));
597+
}
589598
cqv.setValueString(ov.getString("value"));
590599
cqv.setDisplayOrder(ov.getInt("displayOrder"));
591600
cqv.setCustomQuestion(cq);

src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,50 @@ public static JsonObjectBuilder getOwnersFromDvObject(DvObject dvObject){
413413
return getOwnersFromDvObject(dvObject, null);
414414
}
415415

416+
public static JsonObjectBuilder json(GuestbookResponse gbResponse) {
417+
JsonObjectBuilder guestbookResponseObject = jsonObjectBuilder();
418+
if (gbResponse != null) {
419+
guestbookResponseObject.add("id", gbResponse.getId());
420+
guestbookResponseObject.add("dataset", gbResponse.getDataset().getDisplayName());
421+
guestbookResponseObject.add("datasetPid", gbResponse.getDataset().getIdentifier());
422+
guestbookResponseObject.add("date", format(gbResponse.getResponseTime()));
423+
guestbookResponseObject.add("type", gbResponse.getEventType());
424+
guestbookResponseObject.add("fileName", gbResponse.getDataFile().getDisplayName());
425+
guestbookResponseObject.add("fileId", gbResponse.getDataFile().getId());
426+
if (gbResponse.getDataFile().getGlobalId() != null) {
427+
guestbookResponseObject.add("filePid", gbResponse.getDataFile().getGlobalId().asString());
428+
}
429+
guestbookResponseObject.add("userName", gbResponse.getAuthenticatedUser() != null ? gbResponse.getAuthenticatedUser().getUserIdentifier() : "Guest");
430+
if (gbResponse.getEmail() != null) {
431+
guestbookResponseObject.add("email", gbResponse.getEmail());
432+
}
433+
if (gbResponse.getInstitution() != null) {
434+
guestbookResponseObject.add("institution", gbResponse.getInstitution());
435+
}
436+
if (gbResponse.getPosition() != null) {
437+
guestbookResponseObject.add("position", gbResponse.getPosition());
438+
}
439+
final List<CustomQuestionResponse> cqResponses = gbResponse.getCustomQuestionResponses();
440+
if (cqResponses != null && !cqResponses.isEmpty()) {
441+
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
442+
for (CustomQuestionResponse cqResponse : cqResponses) {
443+
JsonObjectBuilder cqObj = jsonObjectBuilder();
444+
cqObj.add("question", cqResponse.getCustomQuestion().getQuestionString());
445+
cqObj.add("response", cqResponse.getResponse());
446+
customQuestions.add(cqObj);
447+
}
448+
guestbookResponseObject.add("customQuestions", customQuestions);
449+
}
450+
}
451+
return guestbookResponseObject;
452+
}
453+
416454
public static JsonObjectBuilder json(Guestbook guestbook) {
417455
JsonObjectBuilder guestbookObject = jsonObjectBuilder();
418456
if (guestbook != null) {
419-
guestbookObject.add("id", guestbook.getId());
457+
if (guestbook.getId() != null) {
458+
guestbookObject.add("id", guestbook.getId());
459+
}
420460
guestbookObject.add("name", guestbook.getName());
421461
guestbookObject.add("enabled", guestbook.isEnabled());
422462
guestbookObject.add("emailRequired", guestbook.isEmailRequired());
@@ -429,15 +469,15 @@ public static JsonObjectBuilder json(Guestbook guestbook) {
429469
if (guestbook.getResponseCount() != null) {
430470
guestbookObject.add("responseCount", guestbook.getResponseCount());
431471
}
432-
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
433-
if (guestbook.getCustomQuestions() != null) {
472+
if (guestbook.getCustomQuestions() != null && !guestbook.getCustomQuestions().isEmpty()) {
473+
JsonArrayBuilder customQuestions = Json.createArrayBuilder();
434474
for (CustomQuestion cq : guestbook.getCustomQuestions()) {
435475
customQuestions.add(json(cq));
436476
}
477+
guestbookObject.add("customQuestions", customQuestions);
437478
}
438-
guestbookObject.add("customQuestions", customQuestions);
439479
if (guestbook.getCreateTime() != null) {
440-
guestbookObject.add("createTime", guestbook.getCreateTime().toString());
480+
guestbookObject.add("createTime", format(guestbook.getCreateTime()));
441481
}
442482
if (guestbook.getDataverse() != null) {
443483
guestbookObject.add("dataverseId", guestbook.getDataverse().getId());
@@ -447,7 +487,9 @@ public static JsonObjectBuilder json(Guestbook guestbook) {
447487
}
448488
public static JsonObjectBuilder json(CustomQuestion customQuestion) {
449489
JsonObjectBuilder customQuestionObject = jsonObjectBuilder();
450-
customQuestionObject.add("id", customQuestion.getId());
490+
if (customQuestion.getId() != null) {
491+
customQuestionObject.add("id", customQuestion.getId());
492+
}
451493
customQuestionObject.add("question", customQuestion.getQuestionString());
452494
customQuestionObject.add("required", customQuestion.isRequired());
453495
customQuestionObject.add("displayOrder", customQuestion.getDisplayOrder());
@@ -457,7 +499,9 @@ public static JsonObjectBuilder json(CustomQuestion customQuestion) {
457499
JsonArrayBuilder customQuestionsValues = Json.createArrayBuilder();
458500
for (CustomQuestionValue value : customQuestion.getCustomQuestionValues()) {
459501
JsonObjectBuilder customQuestionValueObject = jsonObjectBuilder();
460-
customQuestionValueObject.add("id", value.getId());
502+
if (value.getId() != null) {
503+
customQuestionValueObject.add("id", value.getId());
504+
}
461505
customQuestionValueObject.add("value", value.getValueString());
462506
customQuestionValueObject.add("displayOrder", value.getDisplayOrder());
463507
customQuestionsValues.add(customQuestionValueObject);

src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4105,6 +4105,32 @@ public void testDownloadFileWithGuestbookResponse() throws IOException, JsonPars
41054105
.statusCode(OK.getStatusCode())
41064106
.body("data[0].usageCount", is(1))
41074107
.body("data[0].responseCount", is(17));
4108+
4109+
// Test Get All Responses
4110+
Response guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), null, null, ownerApiToken);
4111+
guestbookListResponses.prettyPrint();
4112+
guestbookListResponses.then().assertThat()
4113+
.statusCode(OK.getStatusCode());
4114+
JsonPath jsonPath = JsonPath.from(guestbookListResponses.body().asString());
4115+
int totalCount = jsonPath.getList("data.responses").size();
4116+
4117+
// Test Get Responses with pagination
4118+
int pages = 4; // total should be 17. set to 4 pages
4119+
int limit = (totalCount / pages) + 1; // should be 5 per page. we should see 5, 5, 5, 2
4120+
int pagedTotalCount = 0;
4121+
int totalCountFromJson = 0;
4122+
for (int i = 0; i < pages; i++) {
4123+
int offset = limit * i;
4124+
guestbookListResponses = UtilIT.getGuestbooksResponses(guestbook.getId(), offset, limit, ownerApiToken);
4125+
guestbookListResponses.prettyPrint();
4126+
jsonPath = JsonPath.from(guestbookListResponses.body().asString());
4127+
pagedTotalCount += jsonPath.getList("data.responses").size();
4128+
totalCountFromJson = jsonPath.getInt("data.pagination.totalResponses");
4129+
// 'No duplicate ids' was manually verified. Just make sure the count is good. If there were duplicates the count would be high
4130+
}
4131+
// verify all counts are good and equal
4132+
assertEquals(totalCount, pagedTotalCount);
4133+
assertEquals(pagedTotalCount, totalCountFromJson);
41084134
}
41094135

41104136
@Test

0 commit comments

Comments
 (0)