Skip to content

Commit fb33fa4

Browse files
authored
Merge pull request #11347 from IQSS/11280-search-api-count-per-object
return all type totals regardless of types being returned
2 parents efbbd18 + c395ac6 commit fb33fa4

File tree

3 files changed

+131
-60
lines changed

3 files changed

+131
-60
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Bug fix to Search API. Now includes all type totals (Dataverses, Dataset, and Files) regardless of the list of types requested
2+
3+
None requested types were returned with total count set to 0.
4+
&type=dataverse&type=dataset would result in "Files" : 0 since type=file was not requested
5+
6+
Now all counts show the correct totals.
7+
Note: This is only true for the first page requested. Subsequent pages could have 0 counts and should not be used. This is due to the need for speed. Getting the totals is an additional search call in the background.

src/main/java/edu/harvard/iq/dataverse/api/Search.java

Lines changed: 51 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import edu.harvard.iq.dataverse.*;
44
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
5+
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
56
import edu.harvard.iq.dataverse.search.SearchFields;
67
import edu.harvard.iq.dataverse.search.FacetCategory;
78
import edu.harvard.iq.dataverse.search.FacetLabel;
@@ -17,10 +18,7 @@
1718
import edu.harvard.iq.dataverse.search.SortBy;
1819
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
1920
import java.io.IOException;
20-
import java.util.ArrayList;
21-
import java.util.Arrays;
22-
import java.util.List;
23-
import java.util.Map;
21+
import java.util.*;
2422
import java.util.logging.Logger;
2523
import jakarta.ejb.EJB;
2624
import jakarta.inject.Inject;
@@ -92,9 +90,50 @@ public Response search(
9290
String geoPoint;
9391
String geoRadius;
9492
List<Dataverse> dataverseSubtrees = new ArrayList<>();
93+
DataverseRequest requestUser = createDataverseRequest(user);
94+
String allTypes = ":(" + SearchConstants.DATAVERSES + " OR " + SearchConstants.DATASETS + " OR " + SearchConstants.FILES + ")";
95+
Map<String, Long> objectTypeCountsMap = new HashMap<>(3);
96+
objectTypeCountsMap.put(SearchConstants.UI_DATAVERSES, 0L);
97+
objectTypeCountsMap.put(SearchConstants.UI_DATASETS, 0L);
98+
objectTypeCountsMap.put(SearchConstants.UI_FILES, 0L);
99+
100+
// users can't change these (yet anyway)
101+
boolean dataRelatedToMe = showMyData; //getDataRelatedToMe();
95102

96103
try {
104+
// we have to add "" (root) otherwise there is no permissions check
105+
if (subtrees.isEmpty()) {
106+
dataverseSubtrees.add(getSubtree(""));
107+
}
108+
else {
109+
for (String subtree : subtrees) {
110+
dataverseSubtrees.add(getSubtree(subtree));
111+
}
112+
}
113+
filterQueries.add(getFilterQueryFromSubtrees(dataverseSubtrees));
114+
97115
if (!types.isEmpty()) {
116+
// Query to get the totals if needed.
117+
// Only needed if the list of types doesn't include all types since missing types will default to count of 0
118+
// Only get the totals for the first page (paginationStart == 0) for speed
119+
if (showTypeCounts && types.size() < objectTypeCountsMap.size() && paginationStart == 0) {
120+
List<String> totalFilterQueries = new ArrayList<>();
121+
totalFilterQueries.addAll(filterQueries);
122+
totalFilterQueries.add(SearchFields.TYPE + allTypes);
123+
try {
124+
SolrQueryResponse resp = searchService.search(requestUser, dataverseSubtrees, query, totalFilterQueries, null, null, 0,
125+
dataRelatedToMe, 1, false, null, null, false, false);
126+
if (resp != null) {
127+
for (FacetCategory facetCategory : resp.getTypeFacetCategories()) {
128+
for (FacetLabel facetLabel : facetCategory.getFacetLabel()) {
129+
objectTypeCountsMap.put(facetLabel.getName(), facetLabel.getCount());
130+
}
131+
}
132+
}
133+
} catch(Exception e) {
134+
logger.info("Search getting total counts: " + e.getMessage());
135+
}
136+
}
98137
filterQueries.add(getFilterQueryFromTypes(types));
99138
} else {
100139
/**
@@ -103,22 +142,11 @@ public Response search(
103142
* SearchServiceBean tries to get SearchFields.TYPE. The GUI
104143
* always seems to add SearchFields.TYPE, even for superusers.
105144
*/
106-
filterQueries.add(SearchFields.TYPE + ":(" + SearchConstants.DATAVERSES + " OR " + SearchConstants.DATASETS + " OR " + SearchConstants.FILES + ")");
145+
filterQueries.add(SearchFields.TYPE + allTypes);
107146
}
108147
sortBy = SearchUtil.getSortBy(sortField, sortOrder);
109148
numResultsPerPage = getNumberOfResultsPerPage(numResultsPerPageRequested);
110149

111-
// we have to add "" (root) otherwise there is no permissions check
112-
if(subtrees.isEmpty()) {
113-
dataverseSubtrees.add(getSubtree(""));
114-
}
115-
else {
116-
for(String subtree : subtrees) {
117-
dataverseSubtrees.add(getSubtree(subtree));
118-
}
119-
}
120-
filterQueries.add(getFilterQueryFromSubtrees(dataverseSubtrees));
121-
122150
if(filterQueries.isEmpty()) { //Extra sanity check just in case someone else touches this
123151
throw new IOException("Filter is empty, which should never happen, as this allows unfettered searching of our index");
124152
}
@@ -137,13 +165,10 @@ public Response search(
137165
} catch (Exception ex) {
138166
return error(Response.Status.BAD_REQUEST, ex.getLocalizedMessage());
139167
}
140-
141-
// users can't change these (yet anyway)
142-
boolean dataRelatedToMe = showMyData; //getDataRelatedToMe();
143168

144169
SolrQueryResponse solrQueryResponse;
145170
try {
146-
solrQueryResponse = searchService.search(createDataverseRequest(user),
171+
solrQueryResponse = searchService.search(requestUser,
147172
dataverseSubtrees,
148173
query,
149174
filterQueries,
@@ -211,50 +236,17 @@ public Response search(
211236
}
212237

213238
value.add("count_in_response", solrSearchResults.size());
214-
215-
// we want to show the missing dvobject types with count = 0
216-
// per https://github.com/IQSS/dataverse/issues/11127
217239

218240
if (showTypeCounts) {
219-
JsonObjectBuilder objectTypeCounts = Json.createObjectBuilder();
220-
if (!solrQueryResponse.getTypeFacetCategories().isEmpty()) {
221-
boolean filesMissing = true;
222-
boolean datasetsMissing = true;
223-
boolean dataversesMissing = true;
224-
for (FacetCategory facetCategory : solrQueryResponse.getTypeFacetCategories()) {
225-
for (FacetLabel facetLabel : facetCategory.getFacetLabel()) {
226-
objectTypeCounts.add(facetLabel.getName(), facetLabel.getCount());
227-
if (facetLabel.getName().equals((SearchConstants.UI_DATAVERSES))) {
228-
dataversesMissing = false;
229-
}
230-
if (facetLabel.getName().equals((SearchConstants.UI_DATASETS))) {
231-
datasetsMissing = false;
232-
}
233-
if (facetLabel.getName().equals((SearchConstants.UI_FILES))) {
234-
filesMissing = false;
235-
}
236-
}
237-
}
238-
239-
if (solrQueryResponse.getTypeFacetCategories().size() < 3) {
240-
if (dataversesMissing) {
241-
objectTypeCounts.add(SearchConstants.UI_DATAVERSES, 0);
242-
}
243-
if (datasetsMissing) {
244-
objectTypeCounts.add(SearchConstants.UI_DATASETS, 0);
245-
}
246-
if (filesMissing) {
247-
objectTypeCounts.add(SearchConstants.UI_FILES, 0);
241+
for (FacetCategory facetCategory : solrQueryResponse.getTypeFacetCategories()) {
242+
for (FacetLabel facetLabel : facetCategory.getFacetLabel()) {
243+
if (facetLabel.getCount() > 0) {
244+
objectTypeCountsMap.put(facetLabel.getName(), facetLabel.getCount());
248245
}
249246
}
250-
251-
}
252-
if (showTypeCounts && solrQueryResponse.getTypeFacetCategories().isEmpty()) {
253-
objectTypeCounts.add(SearchConstants.UI_DATAVERSES, 0);
254-
objectTypeCounts.add(SearchConstants.UI_DATASETS, 0);
255-
objectTypeCounts.add(SearchConstants.UI_FILES, 0);
256247
}
257-
248+
JsonObjectBuilder objectTypeCounts = Json.createObjectBuilder();
249+
objectTypeCountsMap.forEach((k,v) -> objectTypeCounts.add(k,v));
258250
value.add("total_count_per_object_type", objectTypeCounts);
259251
}
260252
/**

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,7 @@ public void testShowTypeCounts() throws InterruptedException {
17671767
Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken, affiliation);
17681768
assertEquals(201, createDataverseResponse.getStatusCode());
17691769
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
1770+
String dataverseWithDatasetsFilesAlias = dataverseAlias;
17701771

17711772
// create 3 Datasets, each with 2 Datafiles
17721773
for (int i = 0; i < 3; i++) {
@@ -1840,7 +1841,35 @@ public void testShowTypeCounts() throws InterruptedException {
18401841
.statusCode(OK.getStatusCode())
18411842
.body("data.total_count_per_object_type.Dataverses", CoreMatchers.is(1))
18421843
.body("data.total_count_per_object_type.Datasets", CoreMatchers.is(0))
1843-
.body("data.total_count_per_object_type.Files", CoreMatchers.is(0));
1844+
.body("data.total_count_per_object_type.Files", CoreMatchers.is(0));
1845+
1846+
// Test Search with show_type_counts = TRUE getting only Dataverses
1847+
searchResp = UtilIT.search(dataverseWithDatasetsFilesAlias, apiToken, "&show_facets=true&type=dataverse&show_type_counts=true");
1848+
searchResp.prettyPrint();
1849+
searchResp.then().assertThat()
1850+
.statusCode(OK.getStatusCode())
1851+
.body("data.count_in_response", CoreMatchers.is(1)) // 1 dataverse
1852+
.body("data.total_count_per_object_type.Dataverses", CoreMatchers.is(1))
1853+
.body("data.total_count_per_object_type.Datasets", CoreMatchers.is(3))
1854+
.body("data.total_count_per_object_type.Files", CoreMatchers.is(6));
1855+
// Test Search with show_type_counts = TRUE getting only Dataverses and Datasets
1856+
searchResp = UtilIT.search(dataverseWithDatasetsFilesAlias, apiToken, "&type=dataverse&type=dataset&show_type_counts=true");
1857+
searchResp.prettyPrint();
1858+
searchResp.then().assertThat()
1859+
.statusCode(OK.getStatusCode())
1860+
.body("data.count_in_response", CoreMatchers.is(4)) // 1 dataverse + 3 datasets
1861+
.body("data.total_count_per_object_type.Dataverses", CoreMatchers.is(1))
1862+
.body("data.total_count_per_object_type.Datasets", CoreMatchers.is(3))
1863+
.body("data.total_count_per_object_type.Files", CoreMatchers.is(6));
1864+
// Test Search with show_type_counts = TRUE getting Dataverses, Datasets and Files
1865+
searchResp = UtilIT.search(dataverseWithDatasetsFilesAlias, apiToken, "&type=dataverse&type=dataset&type=file&show_type_counts=true");
1866+
searchResp.prettyPrint();
1867+
searchResp.then().assertThat()
1868+
.statusCode(OK.getStatusCode())
1869+
.body("data.count_in_response", CoreMatchers.is(10)) // 1 dataverse + 3 datasets + 6 files
1870+
.body("data.total_count_per_object_type.Dataverses", CoreMatchers.is(1))
1871+
.body("data.total_count_per_object_type.Datasets", CoreMatchers.is(3))
1872+
.body("data.total_count_per_object_type.Files", CoreMatchers.is(6));
18441873
}
18451874

18461875
@Test
@@ -1891,4 +1920,47 @@ public void testTabularFiles() throws IOException {
18911920
.body("data.items[0].observations", is(3));
18921921
}
18931922

1923+
@Test
1924+
public void testTotalCount() throws IOException {
1925+
Response createUser = UtilIT.createRandomUser();
1926+
createUser.then().assertThat().statusCode(OK.getStatusCode());
1927+
String username = UtilIT.getUsernameFromResponse(createUser);
1928+
Response makeSuperUser = UtilIT.makeSuperUser(username);
1929+
assertEquals(200, makeSuperUser.getStatusCode());
1930+
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
1931+
1932+
Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
1933+
createDataverseResponse.prettyPrint();
1934+
createDataverseResponse.then().assertThat()
1935+
.statusCode(CREATED.getStatusCode());
1936+
1937+
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
1938+
1939+
Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
1940+
createDataset.prettyPrint();
1941+
createDataset.then().assertThat()
1942+
.statusCode(CREATED.getStatusCode());
1943+
1944+
Integer datasetId = UtilIT.getDatasetIdFromResponse(createDataset);
1945+
String datasetPid = UtilIT.getDatasetPersistentIdFromResponse(createDataset);
1946+
1947+
Path pathToDataFile = Paths.get(java.nio.file.Files.createTempDirectory(null) + File.separator + "data.csv");
1948+
String contentOfCsv = ""
1949+
+ "name,pounds,species,treats\n"
1950+
+ "Midnight,15,dog,milkbones\n"
1951+
+ "Tiger,17,cat,cat grass\n"
1952+
+ "Panther,21,cat,cat nip\n";
1953+
java.nio.file.Files.write(pathToDataFile, contentOfCsv.getBytes());
1954+
1955+
Response uploadFile = UtilIT.uploadFileViaNative(datasetId.toString(), pathToDataFile.toString(), apiToken);
1956+
uploadFile.prettyPrint();
1957+
uploadFile.then().assertThat()
1958+
.statusCode(OK.getStatusCode())
1959+
.body("data.files[0].label", equalTo("data.csv"));
1960+
1961+
Response searchResponse = UtilIT.search("&show_facets=true&sort=date&order=desc&show_type_counts=true&subtree=root&per_page=10&type=dataverse&type=dataset", apiToken);
1962+
searchResponse.prettyPrint();
1963+
searchResponse.then().assertThat()
1964+
.statusCode(OK.getStatusCode());
1965+
}
18941966
}

0 commit comments

Comments
 (0)