Skip to content
Open
3 changes: 3 additions & 0 deletions doc/release-notes/11447-mydata-retrieve-empty-result-set.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions doc/sphinx-guides/source/api/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
73 changes: 38 additions & 35 deletions src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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();
}

// ---------------------------------
Expand All @@ -226,7 +219,7 @@ public String retrieveMyDataAsJsonString(
List<String> 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);

Expand Down Expand Up @@ -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) {
Expand All @@ -281,30 +274,14 @@ 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);

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
Expand All @@ -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<String> 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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class SolrQueryResponse {

private static final Logger logger = Logger.getLogger(SolrQueryResponse.class.getCanonicalName());

private List<SolrSearchResult> solrSearchResults;
private Long numResultsFound;
private Long resultsStart;
private List<SolrSearchResult> solrSearchResults = List.of();
private Long numResultsFound = 0L;
private Long resultsStart = 0L;
private Map<String, List<String>> spellingSuggestionsByToken;
private List<FacetCategory> facetCategoryList;
private List<FacetCategory> typeFacetCategories;
Expand Down
5 changes: 5 additions & 0 deletions src/main/webapp/resources/js/mydata.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 27 additions & 8 deletions src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -53,20 +54,26 @@ 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
Response createNormalUserResponse = UtilIT.createRandomUser();
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -525,4 +535,13 @@ private static String prettyPrintError(String resourceBundleKey, List<String> pa
}
return String.format(ERR_MSG_FORMAT, errorMessage.replaceAll("\"", "\\\\\""));
}
private static String prettyPrintMessage(String resourceBundleKey, List<String> params) {
final String message;
if (params == null || params.isEmpty()) {
message = BundleUtil.getStringFromBundle(resourceBundleKey);
} else {
message = BundleUtil.getStringFromBundle(resourceBundleKey, params);
}
return message.replaceAll("\"", "\\\\\"");
}
}
Loading