diff --git a/doc/release-notes/12512-file-citation-version.md b/doc/release-notes/12512-file-citation-version.md new file mode 100644 index 00000000000..df500c8b192 --- /dev/null +++ b/doc/release-notes/12512-file-citation-version.md @@ -0,0 +1,3 @@ +## API Updates + +- When downloading a file citation it is now possible to specify the version of the dataset. See [the guides](https://guides.dataverse.org/en/6.13/api/dataaccess.html#citation-get-citation-in-other-formats) and #12512. \ No newline at end of file diff --git a/doc/sphinx-guides/source/api/dataaccess.rst b/doc/sphinx-guides/source/api/dataaccess.rst index 505556cdea6..f67bcf17336 100755 --- a/doc/sphinx-guides/source/api/dataaccess.rst +++ b/doc/sphinx-guides/source/api/dataaccess.rst @@ -271,8 +271,12 @@ Usage example: export SERVER_URL=https://demo.dataverse.org export DATAFILE_ID=99 export FORMAT=EndNote + export VERSION=1.0 + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/access/datafile/$DATAFILE_ID/citation/$FORMAT?version=$VERSION" - curl "$SERVER_URL/api/access/datafile/$DATAFILE_ID/citation/$FORMAT" +The ``version`` query parameter is optional. It will default to ``:latest-published``. See :ref:`dataset-version-specifiers` for the list of possible values. If you request a draft, an API token with access must be provided. .. _data-variable-metadata-access: diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index cdb27fd5f21..b43076e1dc5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -146,7 +146,9 @@ public Response datafileCitation(@Context ContainerRequestContext crc, @Parameter(description = "Data file id or persistent identifier.", required = true) @PathParam("fileId") String fileId, @Parameter(description = "Citation format to return.") - @PathParam("format") String formatString) { + @PathParam("format") String formatString, + @Parameter(description = "Dataset version, such as 1.0, :draft, :latest, or :latest-published. If omitted, uses :latest-published.", required = false) + @QueryParam("version") String version) { DataCitation.Format format = DataCitation.getFormat(formatString); if (format == null) { @@ -158,7 +160,23 @@ public Response datafileCitation(@Context ContainerRequestContext crc, // This will throw a ForbiddenException if access isn't authorized: checkAuthorization(req.getUser(), df); - String dataCitationFormatted = (new DataCitation(df.getFileMetadata())).toString(format, true, false); + // Default to latest file metadata. + FileMetadata fileMetadata = df.getFileMetadata(); + if (version != null) { + try { + DatasetVersion datasetVersion = getDatasetVersionFromVersion(crc, df.getOwner().getId().toString(), version); + if (datasetVersion == null) { + return badRequest("Dataset version not found: " + version); + } + fileMetadata = dataFileService.findFileMetadataByDatasetVersionIdAndDataFileId(datasetVersion.getId(), df.getId()); + if (fileMetadata == null) { + return badRequest("File not found in dataset version: " + version); + } + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + } + String dataCitationFormatted = (new DataCitation(fileMetadata)).toString(format, true, false); return Response.ok().type(DataCitation.getCitationFormatMediaType(format, true)).entity(dataCitationFormatted).build(); } 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 ca8af25044c..d25be034197 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java @@ -4284,6 +4284,68 @@ public void testGetFileCitationFormatted() { .statusCode(OK.getStatusCode()); } + @Test + public void testGetFileCitationFormattedWithVersion() { + Response createUser = UtilIT.createRandomUser(); + createUser.then().statusCode(OK.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverse = UtilIT.createRandomDataverse(apiToken); + createDataverse.then().statusCode(CREATED.getStatusCode()); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse); + + Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDataset.then().statusCode(CREATED.getStatusCode()); + + Integer datasetId = createDataset.jsonPath().getInt("data.id"); + + Response uploadFile = UtilIT.uploadFileViaNative(datasetId.toString(), + "src/test/resources/images/coffeeshop.png", JsonUtil.createObjectBuilder().build(), apiToken); + uploadFile.then().statusCode(OK.getStatusCode()); + + String fileId = uploadFile.jsonPath().getString("data.files[0].dataFile.id"); + + UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().statusCode(OK.getStatusCode()); + UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().statusCode(OK.getStatusCode()); + UtilIT.updateFileMetadata(fileId, "{\"label\":\"renamed.png\"}", apiToken) + .then().statusCode(OK.getStatusCode()); + + // The same file has different metadata in the published version and the draft. + UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.0").then() + .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png")); + UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":draft").then() + .statusCode(OK.getStatusCode()).body(containsString("renamed.png")); + UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":latest").then() + .statusCode(OK.getStatusCode()).body(containsString("renamed.png")); + // No API token given so the published filename is shown + UtilIT.getFileCitationFormat(fileId, "EndNote", null, ":latest").then() + .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png")); + UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":latest-published").then() + .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png")); + UtilIT.getFileCitationFormat(fileId, "EndNote", null, ":draft").then() + .statusCode(UNAUTHORIZED.getStatusCode()); + + UtilIT.publishDatasetViaNativeApi(datasetId, "minor", apiToken).then().statusCode(OK.getStatusCode()); + UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.1").then() + .statusCode(OK.getStatusCode()).body(containsString("renamed.png")); + // Historical citations still use the original file metadata after another release. + UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.0").then() + .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png")); + UtilIT.getFileCitationFormat(fileId, "RIS", null, "1.0").then() + .statusCode(OK.getStatusCode()).body(containsString("C1 - coffeeshop.png")); + UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, "666.0").then() + .statusCode(BAD_REQUEST.getStatusCode()); + UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, "invalid").then() + .statusCode(BAD_REQUEST.getStatusCode()); + + Response uploadNewFile = UtilIT.uploadFileViaNative(datasetId.toString(), + "src/main/webapp/resources/images/dataverseproject.png", JsonUtil.createObjectBuilder().build(), apiToken); + uploadNewFile.then().statusCode(OK.getStatusCode()); + String newFileId = uploadNewFile.jsonPath().getString("data.files[0].dataFile.id"); + UtilIT.getFileCitationFormat(newFileId, "EndNote", apiToken, "1.0").then() + .statusCode(BAD_REQUEST.getStatusCode()).body("message", equalTo("File not found in dataset version: 1.0")); + } + // This test is disabled because it is only compatible with the containerized development environment and would cause the Jenkins job to fail. @Test @Disabled 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 1c1d7116dc3..106bdea16b1 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1508,10 +1508,17 @@ static Response getFileData(String fileId, String apiToken, String datasetVersio } static Response getFileCitationFormat(String dataFileId, String format, String apiToken) { + return getFileCitationFormat(dataFileId, format, apiToken, null); + } + + static Response getFileCitationFormat(String dataFileId, String format, String apiToken, String version) { RequestSpecification request = given(); if (apiToken != null) { request.header(API_TOKEN_HTTP_HEADER, apiToken); } + if (version != null) { + request.queryParam("version", version); + } return request.get("/api/access/datafile/" + dataFileId + "/citation/" + format); }