Skip to content

Commit daa5634

Browse files
authored
Merge pull request IQSS#12009 from vera/feat/my-data-api-params
feat: add metadata_fields, sort, show_collections and fq params to "my data" API
2 parents c2a4ed6 + 947638b commit daa5634

File tree

9 files changed

+169
-22
lines changed

9 files changed

+169
-22
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The My Data API now supports the `metadata_fields`, `sort` and `order`, `show_collections` and `fq` parameters, which enhances its functionality and brings it in line with the search API.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8734,6 +8734,16 @@ Parameters:
87348734
87358735
``per_page`` Number of results returned per page.
87368736
8737+
``metadata_fields`` Includes the requested fields for each dataset in the response. Multiple "metadata_fields" parameters can be used to include several fields. See :doc:`search` for further information on this parameter.
8738+
8739+
``show_collections`` Whether or not to include a list of parent and linked collections for each dataset search result.
8740+
8741+
``sort`` The sort field. Supported values include "name", "date" and "relevance".
8742+
8743+
``order`` The order in which to sort. Can either be "asc" or "desc".
8744+
8745+
``fq`` A filter query to filter the list returned. Multiple "fq" parameters can be used.
8746+
87378747
MyData Collection List
87388748
~~~~~~~~~~~~~~~~~~~~~~
87398749

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Name Type Description
2727
q string The search term or terms. Using "title:data" will search only the "title" field. "*" can be used as a wildcard either alone or adjacent to a term (i.e. "bird*"). For example, https://demo.dataverse.org/api/search?q=title:data . For a list of fields to search, please see https://github.com/IQSS/dataverse/issues/2558 (for now).
2828
type string Can be either "dataverse", "dataset", or "file". Multiple "type" parameters can be used to include multiple types (i.e. ``type=dataset&type=file``). If omitted, all types will be returned. For example, https://demo.dataverse.org/api/search?q=*&type=dataset
2929
subtree string The identifier of the Dataverse collection to which the search should be narrowed. The subtree of this Dataverse collection and all its children will be searched. Multiple "subtree" parameters can be used to include multiple Dataverse collections. For example, https://demo.dataverse.org/api/search?q=data&subtree=birds&subtree=cats .
30-
sort string The sort field. Supported values include "name" and "date". See example under "order".
30+
sort string The sort field. Supported values include "name", "date" and "relevance". See example under "order".
3131
order string The order in which to sort. Can either be "asc" or "desc". For example, https://demo.dataverse.org/api/search?q=data&sort=name&order=asc
3232
per_page int The number of results to return per request. The default is 10. The max is 1000. See :ref:`iteration example <iteration-example>`.
3333
start int A cursor for paging through search results. See :ref:`iteration example <iteration-example>`.

src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@
99
import edu.harvard.iq.dataverse.authorization.users.GuestUser;
1010
import edu.harvard.iq.dataverse.authorization.users.User;
1111
import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand;
12-
import edu.harvard.iq.dataverse.search.SolrQueryResponse;
13-
import edu.harvard.iq.dataverse.search.SolrSearchResult;
12+
import edu.harvard.iq.dataverse.search.*;
1413
import edu.harvard.iq.dataverse.api.AbstractApiBean;
1514
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
1615
import edu.harvard.iq.dataverse.authorization.DataverseRole;
1716
import edu.harvard.iq.dataverse.authorization.DataverseRolePermissionHelper;
1817
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
1918
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
2019
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
21-
import edu.harvard.iq.dataverse.search.SearchConstants;
22-
import edu.harvard.iq.dataverse.search.SearchException;
23-
import edu.harvard.iq.dataverse.search.SearchFields;
24-
import edu.harvard.iq.dataverse.search.SearchServiceFactory;
25-
import edu.harvard.iq.dataverse.search.SortBy;
2620

2721
import java.util.Arrays;
2822
import java.util.List;
@@ -154,13 +148,18 @@ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) thr
154148
public String retrieveMyDataAsJsonString(
155149
@Context ContainerRequestContext crc,
156150
@QueryParam("dvobject_types") List<DvObject.DType> dvobject_types,
157-
@QueryParam("published_states") List<String> published_states,
151+
@QueryParam("published_states") List<String> published_states,
152+
@QueryParam("metadata_fields") List<String> metadataFields,
158153
@QueryParam("selected_page") Integer selectedPage,
159154
@QueryParam("mydata_search_term") String searchTerm,
160155
@QueryParam("role_ids") List<Long> roleIds,
161156
@QueryParam("userIdentifier") String userIdentifier,
162157
@QueryParam("filter_validities") Boolean filterValidities,
163-
@QueryParam("dataset_valid") List<Boolean> datasetValidities) {
158+
@QueryParam("dataset_valid") List<Boolean> datasetValidities,
159+
@QueryParam("show_collections") boolean showCollections,
160+
@QueryParam("sort") String sortField,
161+
@QueryParam("order") String sortOrder,
162+
@QueryParam("fq") final List<String> filterQueries) {
164163
boolean otherUser;
165164

166165
String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound");
@@ -224,23 +223,37 @@ public String retrieveMyDataAsJsonString(
224223

225224
//msg("search with user: " + searchUser.getIdentifier());
226225

227-
List<String> filterQueries = this.myDataFinder.getSolrFilterQueries();
228-
if (filterQueries==null){
226+
List<String> defaultFilterQueries = this.myDataFinder.getSolrFilterQueries();
227+
if (defaultFilterQueries==null){
229228
logger.fine("No ids found for this search");
230229
return this.getJSONErrorString(noMsgResultsFound, null);
231230
}
231+
filterQueries.addAll(defaultFilterQueries);
232+
233+
SortBy sortBy;
234+
try {
235+
sortBy = SearchUtil.getSortBy(sortField, sortOrder, SearchFields.RELEASE_OR_CREATE_DATE);
236+
} catch (Exception ex) {
237+
return this.getJSONErrorString(ex.getLocalizedMessage(), null);
238+
}
232239

233240
try {
234241
solrQueryResponse = searchService.getDefaultSearchService().search(
235242
dataverseRequest,
236243
null, // subtree, default it to Dataverse for now
237244
filterParams.getSearchTerm(), //"*", //
238245
filterQueries,//filterQueries,
239-
//SearchFields.NAME_SORT, SortBy.ASCENDING,
240-
SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING,
246+
sortBy.getField(),
247+
sortBy.getOrder(),
241248
solrCardStart, //paginationStart,
242249
true, // dataRelatedToMe
243-
SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE
250+
SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE
251+
true,
252+
null,
253+
null,
254+
true,
255+
true,
256+
showCollections
244257
);
245258

246259
if (this.solrQueryResponse.getNumResultsFound()==0){
@@ -284,7 +297,7 @@ public String retrieveMyDataAsJsonString(
284297
Json.createObjectBuilder()
285298
.add("pagination", pager.asJsonObjectBuilderUsingCardTerms())
286299
//.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, filterParams, this.myDataFinder))
287-
.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever))
300+
.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever, metadataFields))
288301
.add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound())
289302
.add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart())
290303
.add("search_term", filterParams.getSearchTerm())
@@ -347,7 +360,7 @@ private JsonObjectBuilder getPublicationStatusCounts(SolrQueryResponse solrRespo
347360
* @param roleTagRetriever
348361
* @return
349362
*/
350-
private JsonArrayBuilder formatSolrDocs(SolrQueryResponse solrResponse, RoleTagRetriever roleTagRetriever ){
363+
private JsonArrayBuilder formatSolrDocs(SolrQueryResponse solrResponse, RoleTagRetriever roleTagRetriever, List<String> metadataFields){
351364
if (solrResponse == null){
352365
throw new NullPointerException("DataRetrieverAPI.formatSolrDocs: solrResponse should not be null");
353366
}
@@ -365,7 +378,7 @@ private JsonArrayBuilder formatSolrDocs(SolrQueryResponse solrResponse, RoleTagR
365378
// (a) Get core card data from solr
366379
// -------------------------------------------
367380

368-
myDataCardInfo = doc.getJsonForMyData(isValid(doc));
381+
myDataCardInfo = doc.getJsonForMyData(isValid(doc), metadataFields);
369382

370383
if (doc.getEntity() != null && !doc.getEntity().isInstanceofDataFile()){
371384
String parentAlias = dataverseService.getParentAliasString(doc);

src/main/java/edu/harvard/iq/dataverse/search/SearchUtil.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,18 @@ public static String getTimestampOrNull(Timestamp timestamp) {
6868
}
6969

7070
public static SortBy getSortBy(String sortField, String sortOrder) throws Exception {
71+
return getSortBy(sortField, sortOrder, SearchFields.RELEVANCE);
72+
}
73+
74+
public static SortBy getSortBy(String sortField, String sortOrder, String defaultSortField) throws Exception {
75+
List<String> allowedDefaultSortFieldValues = SortBy.allowedFieldStrings();
76+
if (!allowedDefaultSortFieldValues.contains(defaultSortField)) {
77+
throw new Exception("The 'defaultSortField' was set to '" + defaultSortField + "' but expected one of " + allowedDefaultSortFieldValues + ".");
78+
}
7179

7280
if (StringUtils.isBlank(sortField)) {
81+
sortField = defaultSortField;
82+
} else if (sortField.equals("relevance")) {
7383
sortField = SearchFields.RELEVANCE;
7484
} else if (sortField.equals("name")) {
7585
// "name" sounds better than "name_sort" so we convert it here so users don't have to pass in "name_sort"

src/main/java/edu/harvard/iq/dataverse/search/SolrSearchResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,9 @@ public JsonArrayBuilder getRelevance() {
438438
*
439439
* @return
440440
*/
441-
public JsonObjectBuilder getJsonForMyData(boolean isValid) {
441+
public JsonObjectBuilder getJsonForMyData(boolean isValid, List<String> metadataFields) {
442442

443-
JsonObjectBuilder myDataJson = json(true, true, true);// boolean showRelevance, boolean showEntityIds, boolean showApiUrls)
443+
JsonObjectBuilder myDataJson = json(true, true, true, metadataFields);
444444

445445
myDataJson.add("publication_statuses", this.getPublicationStatusesAsJSON())
446446
.add("is_draft_state", this.isDraftState()).add("is_in_review_state", this.isInReviewState())

src/main/java/edu/harvard/iq/dataverse/search/SortBy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public static List<String> allowedOrderStrings() {
1414
}
1515

1616
private final String field;
17+
18+
public static List<String> allowedFieldStrings() {
19+
return Arrays.asList(SearchFields.RELEVANCE, SearchFields.NAME_SORT, SearchFields.RELEASE_OR_CREATE_DATE);
20+
}
21+
1722
private final String order;
1823

1924
public SortBy(String field, String order) {

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import edu.harvard.iq.dataverse.util.BundleUtil;
88

99
import io.restassured.path.json.JsonPath;
10+
import org.hamcrest.CoreMatchers;
11+
import org.hamcrest.Matchers;
1012
import org.junit.jupiter.api.BeforeAll;
1113
import org.junit.jupiter.api.Test;
1214

@@ -407,6 +409,108 @@ public void testRetrieveMyDataAsJsonStringSortOrder() {
407409
assertEquals(OK.getStatusCode(), deleteSuperUserResponse.getStatusCode());
408410
}
409411

412+
@Test
413+
public void testRetrieveMyDataWithMetadataFields() {
414+
415+
Response createUser = UtilIT.createRandomUser();
416+
createUser.prettyPrint();
417+
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
418+
419+
Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
420+
createDataverseResponse.prettyPrint();
421+
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
422+
423+
Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
424+
createDatasetResponse.prettyPrint();
425+
426+
Response myDataWithAuthor = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&metadata_fields=citation:author");
427+
myDataWithAuthor.prettyPrint();
428+
myDataWithAuthor.then().assertThat()
429+
.body("data.items[0].metadataBlocks.citation.displayName", CoreMatchers.equalTo("Citation Metadata"))
430+
.body("data.items[0].metadataBlocks.citation.fields[0].typeName", CoreMatchers.equalTo("author"))
431+
.body("data.items[0].metadataBlocks.citation.fields[0].value[0].authorName.value", CoreMatchers.equalTo("Finch, Fiona"))
432+
.body("data.items[0].metadataBlocks.citation.fields[0].value[0].authorAffiliation.value", CoreMatchers.equalTo("Birds Inc."))
433+
.statusCode(OK.getStatusCode());
434+
435+
Response subFieldsNotSupported = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&metadata_fields=citation:authorAffiliation");
436+
subFieldsNotSupported.prettyPrint();
437+
subFieldsNotSupported.then().assertThat()
438+
.body("data.items[0].metadataBlocks.citation.displayName", CoreMatchers.equalTo("Citation Metadata"))
439+
// No fields returned. authorAffiliation is a subfield of author and not supported.
440+
.body("data.items[0].metadataBlocks.citation.fields", Matchers.empty())
441+
.statusCode(OK.getStatusCode());
442+
443+
Response myDataWithAllFieldsFromCitation = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&metadata_fields=citation:*");
444+
// Many more fields printed
445+
myDataWithAllFieldsFromCitation.prettyPrint();
446+
myDataWithAllFieldsFromCitation.then().assertThat()
447+
.body("data.items[0].metadataBlocks.citation.displayName", CoreMatchers.equalTo("Citation Metadata"))
448+
// Many fields returned, all of the citation block that has been filled in.
449+
.body("data.items[0].metadataBlocks.citation.fields", Matchers.hasSize(5))
450+
.statusCode(OK.getStatusCode());
451+
452+
}
453+
454+
@Test
455+
public void testRetrieveMyDataWithCollections() {
456+
Response createUser = UtilIT.createRandomUser();
457+
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
458+
459+
Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
460+
createDataverseResponse.prettyPrint();
461+
createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode());
462+
JsonPath createdDataverse = JsonPath.from(createDataverseResponse.body().asString());
463+
String dataverseName = createdDataverse.getString("data.name");
464+
String dataverseAlias = createdDataverse.getString("data.alias");
465+
Integer dataverseId = createdDataverse.getInt("data.id");
466+
467+
UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().assertThat().statusCode(OK.getStatusCode());
468+
469+
Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
470+
createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode());
471+
JsonPath createdDataset = JsonPath.from(createDatasetResponse.body().asString());
472+
int datasetId = createdDataset.getInt("data.id");
473+
String datasetPid = createdDataset.getString("data.persistentId");
474+
475+
UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().assertThat().statusCode(OK.getStatusCode());
476+
477+
// Test that the Dataverse collection that the dataset was created in is returned
478+
Response myDataResponse = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&show_collections=true");
479+
myDataResponse.prettyPrint();
480+
myDataResponse.then().assertThat()
481+
.statusCode(OK.getStatusCode())
482+
.body("data.items[0].collections.size()", CoreMatchers.is(1))
483+
.body("data.items[0].collections[0].id", CoreMatchers.is(dataverseId))
484+
.body("data.items[0].collections[0].name", CoreMatchers.is(dataverseName))
485+
.body("data.items[0].collections[0].alias", CoreMatchers.is(dataverseAlias));
486+
487+
Response createDataverse2Response = UtilIT.createRandomDataverse(apiToken);
488+
createDataverse2Response.prettyPrint();
489+
createDataverse2Response.then().assertThat().statusCode(CREATED.getStatusCode());
490+
JsonPath createDataverse2 = JsonPath.from(createDataverse2Response.body().asString());
491+
String dataverse2Name = createDataverse2.getString("data.name");
492+
String dataverse2Alias = createDataverse2.getString("data.alias");
493+
Integer dataverse2Id = createDataverse2.getInt("data.id");
494+
495+
UtilIT.publishDataverseViaNativeApi(dataverse2Alias, apiToken).then().assertThat().statusCode(OK.getStatusCode());
496+
497+
UtilIT.linkDataset(datasetPid, dataverse2Alias, apiToken).then().assertThat().statusCode(OK.getStatusCode());
498+
499+
UtilIT.sleepForReindex(String.valueOf(datasetId), apiToken, 5);
500+
501+
// Test that the Dataverse collection that the dataset was linked to is also returned
502+
myDataResponse = UtilIT.retrieveMyDataAsJsonString(apiToken, "", new ArrayList<>(Arrays.asList(6L)), "&show_collections=true");
503+
myDataResponse.prettyPrint();
504+
myDataResponse.then().assertThat()
505+
.statusCode(OK.getStatusCode())
506+
.body("data.items[0].collections.size()", CoreMatchers.is(2))
507+
.body("data.items[0].collections", CoreMatchers.hasItems(
508+
Map.of("id", dataverseId, "name", dataverseName, "alias", dataverseAlias),
509+
Map.of("id", dataverse2Id, "name", dataverse2Name, "alias", dataverse2Alias)
510+
));
511+
512+
}
513+
410514
private static String prettyPrintError(String resourceBundleKey, List<String> params) {
411515
final String errorMessage;
412516
if (params == null || params.isEmpty()) {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4170,17 +4170,21 @@ static Response importDatasetViaNativeApi(String apiToken, String dataverseAlias
41704170
}
41714171

41724172

4173-
static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, ArrayList<Long> roleIds) {
4173+
static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, ArrayList<Long> roleIds, String parameterString) {
41744174
Response response = given()
41754175
.header(API_TOKEN_HTTP_HEADER, apiToken)
41764176
.contentType("application/json; charset=utf-8")
41774177
.queryParam("role_ids", roleIds)
41784178
.queryParam("dvobject_types", MyDataFilterParams.defaultDvObjectTypes)
41794179
.queryParam("published_states", MyDataFilterParams.defaultPublishedStates)
4180-
.get("/api/mydata/retrieve?userIdentifier=" + userIdentifier);
4180+
.get("/api/mydata/retrieve?userIdentifier=" + userIdentifier + parameterString);
41814181
return response;
41824182
}
41834183

4184+
static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, ArrayList<Long> roleIds) {
4185+
return retrieveMyDataAsJsonString(apiToken, userIdentifier, roleIds, "");
4186+
}
4187+
41844188
static Response retrieveMyCollectionList(String apiToken, String userIdentifier) {
41854189
RequestSpecification requestSpecification = given();
41864190
if (apiToken != null) {

0 commit comments

Comments
 (0)