diff --git a/doc/release-notes/11387-modify-input-level-api.md b/doc/release-notes/11387-modify-input-level-api.md new file mode 100644 index 00000000000..ca18eabb515 --- /dev/null +++ b/doc/release-notes/11387-modify-input-level-api.md @@ -0,0 +1,3 @@ +### Update Collection Input Level API Changed + +- This endpoint will no longer delete the custom input levels previously modified for the given collection. In order to update a previously modified custom input level, it must be included in the JSON provided to the api. diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst index 5be6c78adce..b793981e761 100644 --- a/doc/sphinx-guides/source/api/changelog.rst +++ b/doc/sphinx-guides/source/api/changelog.rst @@ -13,6 +13,7 @@ v6.8 - For POST /api/files/{id}/metadata passing an empty string ("description":"") or array ("categories":[]) will no longer be ignored. Empty fields will now clear out the values in the file's metadata. To ignore the fields simply do not include them in the JSON string. - For PUT /api/datasets/{id}/editMetadata the query parameter "sourceInternalVersionNumber" has been removed and replaced with "sourceLastUpdateTime" to verify that the data being edited hasn't been modified and isn't stale. - For GET /api/dataverses/$dataverse-alias/links the Json response has changed breaking the backward compatibility of the API. +- For PUT /api/dataverses/$dataverse-alias/inputLevels custom input levels that had been previously set will no longer be deleted. To delete input levels send an empty list (deletes all), then send the new/modified list. - For GET /api/externalTools and /api/externalTools/{id} the responses are now formatted as JSON (previously the toolParameters and allowedApiCalls were a JSON object and array (respectively) that were serialized as JSON strings) and any configured "requirements" are included. v6.7 diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index fa4b4611559..19a4d79c5ae 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -1106,7 +1106,8 @@ Update Collection Input Levels Updates the dataset field type input levels in a collection. -Please note that this endpoint overwrites all the input levels of the collection page, so if you want to keep the existing ones, you will need to add them to the JSON request body. +Please note that this endpoint does not change previously updated input levels of the collection page, so if you want to add new levels or modify existing ones, you will need to include them in the JSON request body. +In order to delete input levels you must call this API with an empty list to delete all of the input levels, then call this API with the new list of input levels. If one of the input levels corresponds to a dataset field type belonging to a metadata block that does not exist in the collection, the metadata block will be added to the collection. diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java index 8227572da3b..e4fd5373c7d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java @@ -105,10 +105,14 @@ private void processInputLevels(CommandContext ctxt) { ctxt.fieldTypeInputLevels().deleteDataverseFieldTypeInputLevelFor(dataverse); } else { dataverse.addInputLevelsMetadataBlocksIfNotPresent(inputLevels); - ctxt.fieldTypeInputLevels().deleteDataverseFieldTypeInputLevelFor(dataverse); + //if levels not empty either create or update (handled by save - update when id not null create if null) inputLevels.forEach(inputLevel -> { + DataverseFieldTypeInputLevel ftil = ctxt.fieldTypeInputLevels().findByDataverseIdDatasetFieldTypeId(dataverse.getId(), inputLevel.getDatasetFieldType().getId()); + if(ftil != null){ + inputLevel.setId(ftil.getId()); + } inputLevel.setDataverse(dataverse); - ctxt.fieldTypeInputLevels().create(inputLevel); + ctxt.fieldTypeInputLevels().save(inputLevel); }); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index 4f9fbae7ffc..155166a386d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -1255,6 +1255,77 @@ public void testUpdateInputLevels() { updateDataverseInputLevelsResponse.then().assertThat() .body("message", equalTo("Error while updating dataverse input levels: Input level list cannot be null or empty")) .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + //Add new types and see that previously changed ones remain as before... #11387 + testInputLevelNames = new String[]{"subtitle", "relatedMaterial"}; + + testRequiredInputLevels = new boolean[] {false, false}; + testIncludedInputLevels = new boolean[] {true, true}; + boolean [] testDisplayOnCreate = new boolean[] {true, false}; + updateDataverseInputLevelsResponse = UtilIT.updateDataverseInputLevels(dataverseAlias, testInputLevelNames, testRequiredInputLevels, testIncludedInputLevels, testDisplayOnCreate, apiToken); + updateDataverseInputLevelsResponse.prettyPrint(); + int subtitleInputLevelIndex = -1; + int relatedMaterialInputLevelIndex = -1; + int i = 0; + + while (updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", i)) != null){ + actualInputLevelName = updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", i)).toString(); + if (actualInputLevelName.equals("subtitle")){ + subtitleInputLevelIndex = i; + } + if (actualInputLevelName.equals("relatedMaterial")){ + relatedMaterialInputLevelIndex = i; + } + i++; + } + + updateDataverseInputLevelsResponse.then().assertThat() + .body(String.format("data.inputLevels[%d].include", subtitleInputLevelIndex), equalTo(true)) + .body(String.format("data.inputLevels[%d].required", subtitleInputLevelIndex), equalTo(false)) + .body(String.format("data.inputLevels[%d].displayOnCreate", subtitleInputLevelIndex), equalTo(true)) + .body(String.format("data.inputLevels[%d].include", relatedMaterialInputLevelIndex), equalTo(true)) + .body(String.format("data.inputLevels[%d].required", relatedMaterialInputLevelIndex), equalTo(false)) + .body(String.format("data.inputLevels[%d].displayOnCreate", relatedMaterialInputLevelIndex), equalTo(false)) + .statusCode(OK.getStatusCode()); + + actualFieldTypeName1 = updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", subtitleInputLevelIndex)); + actualFieldTypeName2 = updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", relatedMaterialInputLevelIndex)); + assertNotEquals(actualFieldTypeName1, actualFieldTypeName2); + assertThat(testInputLevelNames, hasItemInArray(actualFieldTypeName1)); + assertThat(testInputLevelNames, hasItemInArray(actualFieldTypeName2)); + + + testInputLevelNames = new String[]{"subtitle", "otherReferences"}; + testRequiredInputLevels = new boolean[] {false, false}; + testIncludedInputLevels = new boolean[] {true, true}; + testDisplayOnCreate = new boolean[] {false, true}; + + updateDataverseInputLevelsResponse = UtilIT.updateDataverseInputLevels(dataverseAlias, testInputLevelNames, testRequiredInputLevels, testIncludedInputLevels, testDisplayOnCreate, apiToken); + updateDataverseInputLevelsResponse.prettyPrint(); + + subtitleInputLevelIndex = 0; + i = 0; + + while (updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", i)) != null) { + actualInputLevelName = updateDataverseInputLevelsResponse.then().extract().path(String.format("data.inputLevels[%d].datasetFieldTypeName", i)).toString(); + if (actualInputLevelName.equals("subtitle")) { + subtitleInputLevelIndex = i; + } + i++; + } + + //make sure subtitle got changed to false + updateDataverseInputLevelsResponse.then().assertThat() + .body(String.format("data.inputLevels[%d].displayOnCreate", subtitleInputLevelIndex), equalTo(false)) + .statusCode(OK.getStatusCode()); + + //make superuser for cleanup + String username = UtilIT.getUsernameFromResponse(createUserResponse); + UtilIT.setSuperuserStatus(username, Boolean.TRUE); + Response deleteDataverse1Response = UtilIT.deleteDataverse(dataverseAlias, apiToken); + deleteDataverse1Response.prettyPrint(); + assertEquals(200, deleteDataverse1Response.getStatusCode()); + } @Test @@ -2256,12 +2327,17 @@ public void testUpdateInputLevelDisplayOnCreateOverride() { .body("data.inputLevels[0].displayOnCreate", equalTo(true)) .body("data.inputLevels[0].datasetFieldTypeName", equalTo("notesText")); + updateResponse = UtilIT.updateDataverseInputLevelDisplayOnCreate( dataverseAlias, "subtitle", true, apiToken); + + String actualInputLevelName = updateResponse.then().extract().path("data.inputLevels[0].datasetFieldTypeName"); + int subtitleInputLevelIndex = actualInputLevelName.equals("subtitle") ? 0 : 1; + updateResponse.prettyPrint(); updateResponse.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data.inputLevels[0].displayOnCreate", equalTo(true)) - .body("data.inputLevels[0].datasetFieldTypeName", equalTo("subtitle")); + .body(String.format("data.inputLevels[%d].displayOnCreate", subtitleInputLevelIndex), equalTo(true)) + .body(String.format("data.inputLevels[%d].datasetFieldTypeName", subtitleInputLevelIndex), equalTo("subtitle")); listMetadataBlocksResponse = UtilIT.listMetadataBlocks(dataverseAlias, true, true, apiToken); listMetadataBlocksResponse.prettyPrint(); @@ -2274,14 +2350,15 @@ public void testUpdateInputLevelDisplayOnCreateOverride() { .body("data[0].displayName", equalTo("Citation Metadata")) .body("data.size()", equalTo(expectedOnlyDisplayedOnCreateNumberOfMetadataBlocks)) .body("data[0].fields.author.childFields.size()", is(4)); - - updateResponse = UtilIT.updateDataverseInputLevelDisplayOnCreate( - dataverseAlias, "subtitle", false, apiToken); + + updateResponse = UtilIT.updateDataverseInputLevelDisplayOnCreate(dataverseAlias, "subtitle", false, apiToken); + actualInputLevelName = updateResponse.then().extract().path("data.inputLevels[0].datasetFieldTypeName"); + int subtitleIndex = actualInputLevelName.equals("subtitle") ? 0 : 1; updateResponse.then().assertThat() - .statusCode(OK.getStatusCode()) - .body("data.inputLevels[0].displayOnCreate", equalTo(false)) - .body("data.inputLevels[0].datasetFieldTypeName", equalTo("subtitle")); - + .body(String.format("data.inputLevels[%d].displayOnCreate", subtitleIndex), equalTo(false)) + .body(String.format("data.inputLevels[%d].datasetFieldTypeName", subtitleIndex), equalTo("subtitle")) + .statusCode(OK.getStatusCode()); + } @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 eba8181e566..e5d3f908349 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4533,6 +4533,18 @@ public static Response updateDataverseInputLevels(String dataverseAlias, String[ .contentType(ContentType.JSON) .put("/api/dataverses/" + dataverseAlias + "/inputLevels"); } + + public static Response updateDataverseInputLevels(String dataverseAlias, String[] inputLevelNames, boolean[] requiredInputLevels, boolean[] includedInputLevels, boolean[] displayOnCreate, String apiToken) { + JsonArrayBuilder inputLevelsArrayBuilder = Json.createArrayBuilder(); + for (int i = 0; i < inputLevelNames.length; i++) { + inputLevelsArrayBuilder.add(createInputLevelObject(inputLevelNames[i], requiredInputLevels[i], includedInputLevels[i], displayOnCreate[i])); + } + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .body(inputLevelsArrayBuilder.build().toString()) + .contentType(ContentType.JSON) + .put("/api/dataverses/" + dataverseAlias + "/inputLevels"); + } private static JsonObjectBuilder createInputLevelObject(String name, boolean required, boolean include) { return Json.createObjectBuilder() @@ -4540,6 +4552,14 @@ private static JsonObjectBuilder createInputLevelObject(String name, boolean req .add("required", required) .add("include", include); } + + private static JsonObjectBuilder createInputLevelObject(String name, boolean required, boolean include, boolean displayOnCreate) { + return Json.createObjectBuilder() + .add("datasetFieldTypeName", name) + .add("required", required) + .add("include", include) + .add("displayOnCreate", displayOnCreate); + } public static Response getOpenAPI(String accept, String format) { Response response = given() diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreateDataverseCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreateDataverseCommandTest.java index 73880b78e7b..2d2fd943d4d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreateDataverseCommandTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreateDataverseCommandTest.java @@ -307,7 +307,7 @@ public void testCustomOptions() throws CommandException { i++; } - assertTrue( dftilsDeleted ); + // assertTrue( dftilsDeleted ); we no longer delete when adding new input levels to preserve previously created for ( DataverseFieldTypeInputLevel dftil : createdDftils ) { assertEquals( result, dftil.getDataverse() ); }