diff --git a/doc/release-notes/11447-mydata-retrieve-empty-result-set.md b/doc/release-notes/11447-mydata-retrieve-empty-result-set.md new file mode 100644 index 00000000000..949da5780c7 --- /dev/null +++ b/doc/release-notes/11447-mydata-retrieve-empty-result-set.md @@ -0,0 +1,3 @@ +## Feature MyData API Endpoint - don't return error for empty result set + +**GET /api/mydata/retrieve** will now return "data" block with 0 results if the result set is empty, Also, the "success" status will be returned as 'true' and the message giving context as to the 0 results will be returned in "message" instead of "error_message". All true errors will still return "success":false and "error_message":"Some error" with no "data" block. diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst index 08f1fe38c68..3a691eb7312 100644 --- a/doc/sphinx-guides/source/api/changelog.rst +++ b/doc/sphinx-guides/source/api/changelog.rst @@ -10,6 +10,8 @@ This API changelog is experimental and we would love feedback on its usefulness. v6.11 ----- +- The GET /api/mydata/retrieve, if the search returns no data, now includes the "data" block with 0 results. The message that was returned in "error_message" will be returned in "message" and the "success" will be `true`. All other errors will continue to reply with "success":false and the error message in "error_message". + - The following API will now return ``403`` if the ``requireFilesToPublishDataset`` flag is set and the dataset version contains 0 files. - **/api/datasets/{Id}/submitForReview** diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 0a889025966..bdc6961358e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -84,6 +84,7 @@ public class DataRetrieverAPI extends AbstractApiBean { public static final String JSON_SUCCESS_FIELD_NAME = "success"; public static final String JSON_ERROR_MSG_FIELD_NAME = "error_message"; + public static final String JSON_MSG_FIELD_NAME = "message"; public static final String JSON_DATA_FIELD_NAME = "data"; /** @@ -190,26 +191,18 @@ public String retrieveMyDataAsJsonString( } // --------------------------------- - // (1) Initialize filterParams and check for Errors + // (1) Initialize filterParams and MyDataFinder and check for Errors // --------------------------------- DataverseRequest dataverseRequest = createDataverseRequest(authUser); - - MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, dtypes, pub_states, roleIds, searchTerm, validities); - if (filterParams.hasError()){ - return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); + myDataFinder = new MyDataFinder(rolePermissionHelper, roleAssigneeService, dvObjectServiceBean, groupService); + myDataFinder.runFindDataSteps(filterParams); + + if (filterParams.hasError()) { + return myDataAsJson(filterParams.getErrorMessage()).build().toString(); } - - // --------------------------------- - // (2) Initialize MyDataFinder and check for Errors - // --------------------------------- - myDataFinder = new MyDataFinder(rolePermissionHelper, - roleAssigneeService, - dvObjectServiceBean, - groupService); - this.myDataFinder.runFindDataSteps(filterParams); - if (myDataFinder.hasError()){ - return this.getJSONErrorString(myDataFinder.getErrorMessage(), myDataFinder.getErrorMessage()); + if (myDataFinder.hasError()) { + return myDataAsJson(myDataFinder.getErrorMessage()).build().toString(); } // --------------------------------- @@ -226,7 +219,7 @@ public String retrieveMyDataAsJsonString( List defaultFilterQueries = this.myDataFinder.getSolrFilterQueries(); if (defaultFilterQueries==null){ logger.fine("No ids found for this search"); - return this.getJSONErrorString(noMsgResultsFound, null); + return myDataAsJson(noMsgResultsFound).build().toString(); } filterQueries.addAll(defaultFilterQueries); @@ -257,7 +250,7 @@ public String retrieveMyDataAsJsonString( ); if (this.solrQueryResponse.getNumResultsFound()==0){ - return this.getJSONErrorString(noMsgResultsFound, null); + return myDataAsJson(noMsgResultsFound).build().toString(); } } catch (SearchException ex) { @@ -281,9 +274,6 @@ public String retrieveMyDataAsJsonString( // - DvObject counts // --------------------------------- - // Initialize JSON response - JsonObjectBuilder jsonData = Json.createObjectBuilder(); - Pager pager = new Pager(solrQueryResponse.getNumResultsFound().intValue(), SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, paginationStart); @@ -291,20 +281,7 @@ public String retrieveMyDataAsJsonString( RoleTagRetriever roleTagRetriever = new RoleTagRetriever(this.rolePermissionHelper, this.roleAssigneeSvc, this.dvObjectServiceBean); roleTagRetriever.loadRoles(dataverseRequest, solrQueryResponse); - - jsonData.add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, true) - .add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, - Json.createObjectBuilder() - .add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) - //.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, filterParams, this.myDataFinder)) - .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever, metadataFields)) - .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) - .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) - .add("search_term", filterParams.getSearchTerm()) - .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) - .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) - .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) - ); + JsonObjectBuilder jsonData = myDataAsJson(null, pager, roleTagRetriever, metadataFields); // --------------------------------------------------------- // We're doing ~another~ solr query here @@ -320,6 +297,32 @@ public String retrieveMyDataAsJsonString( return jsonData.build().toString(); } + // For empty data to prevent null pointer exceptions in all the dependencies + private JsonObjectBuilder myDataAsJson(String message) { + solrQueryResponse = new SolrQueryResponse(null); + return myDataAsJson(message, new Pager(0, SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, 1), + new RoleTagRetriever(this.rolePermissionHelper, this.roleAssigneeSvc, this.dvObjectServiceBean), List.of()); + } + + private JsonObjectBuilder myDataAsJson(String message, Pager pager, RoleTagRetriever roleTagRetriever, List metadataFields) { + JsonObjectBuilder jsonData = Json.createObjectBuilder().add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, true); + if (message != null) { + jsonData.add(DataRetrieverAPI.JSON_MSG_FIELD_NAME, message); + } + jsonData.add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, + Json.createObjectBuilder() + .add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) + .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever, metadataFields)) + .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) + .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) + .add("search_term", myDataFinder.filterParams.getSearchTerm()) + .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) + .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) + .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) + ); + return jsonData; + } + @GET @AuthRequired @Path(retrieveDataPartialAPIPath + "/collectionList") diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java index 091fbde484e..36cb38419a3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java @@ -424,7 +424,10 @@ public JsonArrayBuilder getListofSelectedRoles(){ JsonArrayBuilder jsonArray = Json.createArrayBuilder(); for (Long roleId : this.filterParams.getRoleIds()){ - jsonArray.add(this.rolePermissionHelper.getRoleName(roleId)); + String roleName = this.rolePermissionHelper.getRoleName(roleId); + if (roleName != null) { + jsonArray.add(roleName); + } } return jsonArray; } diff --git a/src/main/java/edu/harvard/iq/dataverse/search/SolrQueryResponse.java b/src/main/java/edu/harvard/iq/dataverse/search/SolrQueryResponse.java index 27e79cb1fc2..8d1924feb0c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/search/SolrQueryResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/search/SolrQueryResponse.java @@ -14,9 +14,9 @@ public class SolrQueryResponse { private static final Logger logger = Logger.getLogger(SolrQueryResponse.class.getCanonicalName()); - private List solrSearchResults; - private Long numResultsFound; - private Long resultsStart; + private List solrSearchResults = List.of(); + private Long numResultsFound = 0L; + private Long resultsStart = 0L; private Map> spellingSuggestionsByToken; private List facetCategoryList; private List typeFacetCategories; diff --git a/src/main/webapp/resources/js/mydata.js b/src/main/webapp/resources/js/mydata.js index c731d6772ac..27d03051f61 100644 --- a/src/main/webapp/resources/js/mydata.js +++ b/src/main/webapp/resources/js/mydata.js @@ -424,6 +424,11 @@ function submit_my_data_search(){ $('#ajaxStatusPanel_start').hide(); return; } + if (data.message != null){ + setWarningAlert(data.message); + $('#ajaxStatusPanel_start').hide(); + return; + } // -------------------------------- // (2b) Looks good, let's make page diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 060fd4a47f2..5b3cbb92212 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -25,6 +25,7 @@ public class DataRetrieverApiIT { private static final String ERR_MSG_FORMAT = "{\n \"success\": false,\n \"error_message\": \"%s\"\n}"; + private static final String MSG_FORMAT = "{\n \"success\": true,\n \"message\": \"%s\"\n}"; @BeforeAll public static void setUpClass() { @@ -53,7 +54,9 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { Response createSecondUserResponse = UtilIT.createRandomUser(); String userIdentifier = UtilIT.getUsernameFromResponse(createSecondUserResponse); Response validUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, userIdentifier, emptyRoleIdsList); - assertEquals(prettyPrintError("myDataFinder.error.result.no.role", null), validUserIdentifierResponse.prettyPrint()); + String resp = validUserIdentifierResponse.prettyPrint(); + assertTrue(resp.contains("\"success\": true")); + assertTrue(resp.contains(prettyPrintMessage("myDataFinder.error.result.no.role", null))); assertEquals(OK.getStatusCode(), validUserIdentifierResponse.getStatusCode()); // Call as normal user with one valid role and no results @@ -61,12 +64,16 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { String normalUserUsername = UtilIT.getUsernameFromResponse(createNormalUserResponse); String normalUserApiToken = UtilIT.getApiTokenFromResponse(createNormalUserResponse); Response noResultwithOneRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L))); - assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")), noResultwithOneRoleResponse.prettyPrint()); + resp = noResultwithOneRoleResponse.prettyPrint(); + assertTrue(resp.contains("\"success\": true")); + assertTrue(resp.contains(prettyPrintMessage("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")))); assertEquals(OK.getStatusCode(), noResultwithOneRoleResponse.getStatusCode()); // Call as normal user with multiple valid roles and no results Response noResultWithMultipleRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L, 6L))); - assertEquals(prettyPrintError("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")), noResultWithMultipleRoleResponse.prettyPrint()); + resp = noResultWithMultipleRoleResponse.prettyPrint(); + assertTrue(resp.contains("\"success\": true")); + assertTrue(resp.contains(prettyPrintMessage("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")))); assertEquals(OK.getStatusCode(), noResultWithMultipleRoleResponse.getStatusCode()); // Call as normal user with one valid dataset role and one dataset result @@ -252,7 +259,10 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { // Call as regular user with no result Response myDataEmptyResponse = UtilIT.retrieveMyDataAsJsonString(userApiToken, "", new ArrayList<>(Arrays.asList(6L))); - assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")), myDataEmptyResponse.prettyPrint()); + //assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")), myDataEmptyResponse.prettyPrint()); + String resp = myDataEmptyResponse.prettyPrint(); + assertTrue(resp.contains("\"success\": true")); + assertTrue(resp.contains(prettyPrintMessage("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")))); assertEquals(OK.getStatusCode(), myDataEmptyResponse.getStatusCode()); // Create and publish a dataverse @@ -317,7 +327,7 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { assertEquals("RELEASED", jsonPathTwoPublishedDatasets.getString("data.items[1].versionState")); // Create new draft version of dataset 1 by updating metadata - String pathToJsonFilePostPub= "doc/sphinx-guides/source/_static/api/dataset-add-metadata-after-pub.json"; + String pathToJsonFilePostPub = "doc/sphinx-guides/source/_static/api/dataset-add-metadata-after-pub.json"; Response addDataToPublishedVersion = UtilIT.addDatasetMetadataViaNative(datasetOnePid, pathToJsonFilePostPub, userApiToken); addDataToPublishedVersion.prettyPrint(); addDataToPublishedVersion.then().assertThat().statusCode(OK.getStatusCode()); @@ -423,9 +433,9 @@ public void testRetrieveMyDataWithMetadataFields() { Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); String datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse).toString(); - + UtilIT.sleepForReindex(datasetId, apiToken, 5); - + Response myDataWithAuthor = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&metadata_fields=citation:author"); myDataWithAuthor.prettyPrint(); myDataWithAuthor.then().assertThat() @@ -478,7 +488,7 @@ public void testRetrieveMyDataWithCollections() { UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode()); UtilIT.sleepForReindex(datasetPid, apiToken, 5); - + // Test that the Dataverse collection that the dataset was created in is returned Response myDataResponse = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&show_collections=true"); myDataResponse.prettyPrint(); @@ -525,4 +535,13 @@ private static String prettyPrintError(String resourceBundleKey, List pa } return String.format(ERR_MSG_FORMAT, errorMessage.replaceAll("\"", "\\\\\"")); } + private static String prettyPrintMessage(String resourceBundleKey, List params) { + final String message; + if (params == null || params.isEmpty()) { + message = BundleUtil.getStringFromBundle(resourceBundleKey); + } else { + message = BundleUtil.getStringFromBundle(resourceBundleKey, params); + } + return message.replaceAll("\"", "\\\\\""); + } }