diff --git a/src/main/java/org/broadinstitute/consent/http/db/DatasetDAO.java b/src/main/java/org/broadinstitute/consent/http/db/DatasetDAO.java index 3b087694f5..b36758229a 100644 --- a/src/main/java/org/broadinstitute/consent/http/db/DatasetDAO.java +++ b/src/main/java/org/broadinstitute/consent/http/db/DatasetDAO.java @@ -11,12 +11,14 @@ import org.broadinstitute.consent.http.db.mapper.DatasetMapper; import org.broadinstitute.consent.http.db.mapper.DatasetPropertyMapper; import org.broadinstitute.consent.http.db.mapper.DatasetReducer; +import org.broadinstitute.consent.http.db.mapper.DatasetSummaryMapper; import org.broadinstitute.consent.http.db.mapper.DictionaryMapper; import org.broadinstitute.consent.http.db.mapper.FileStorageObjectMapperWithFSOPrefix; import org.broadinstitute.consent.http.models.ApprovedDataset; import org.broadinstitute.consent.http.models.Dataset; import org.broadinstitute.consent.http.models.DatasetAudit; import org.broadinstitute.consent.http.models.DatasetProperty; +import org.broadinstitute.consent.http.models.DatasetSummary; import org.broadinstitute.consent.http.models.Dictionary; import org.broadinstitute.consent.http.models.FileStorageObject; import org.broadinstitute.consent.http.models.Study; @@ -780,4 +782,17 @@ SELECT voteid, MAX(updatedate) update_date OR (dp.schema_property = 'dataCustodianEmail' AND LOWER(dp.property_value) = LOWER(:email)) """) List findDatasetsByCustodian(@Bind("userId") Integer userId, @Bind("email") String email); + + @RegisterRowMapper(DatasetSummaryMapper.class) + @SqlQuery(""" + SELECT DISTINCT d.dataset_id, d.alias, d.name + FROM dataset d + LEFT JOIN dataset_property p ON p.dataset_id = d.dataset_id + WHERE d.dac_approval = TRUE + AND ( + LOWER(d.name) LIKE concat('%', LOWER(:query), '%') OR + LOWER(p.property_value) LIKE concat('%', LOWER(:query), '%') + ) + """) + List findDatasetSummariesByQuery(@Bind("query") String query); } diff --git a/src/main/java/org/broadinstitute/consent/http/db/mapper/DatasetSummaryMapper.java b/src/main/java/org/broadinstitute/consent/http/db/mapper/DatasetSummaryMapper.java new file mode 100644 index 0000000000..df1af4217c --- /dev/null +++ b/src/main/java/org/broadinstitute/consent/http/db/mapper/DatasetSummaryMapper.java @@ -0,0 +1,20 @@ +package org.broadinstitute.consent.http.db.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import org.broadinstitute.consent.http.models.Dataset; +import org.broadinstitute.consent.http.models.DatasetSummary; +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +public class DatasetSummaryMapper implements RowMapper, RowMapperHelper { + + @Override + public DatasetSummary map(ResultSet rs, StatementContext ctx) throws SQLException { + if (hasColumn(rs, "dataset_id") && hasColumn(rs, "name") && hasColumn(rs, "alias")) { + String identifier = Dataset.parseAliasToIdentifier(rs.getInt("alias")); + return new DatasetSummary(rs.getInt("dataset_id"), identifier, rs.getString("name")); + } + return null; + } +} diff --git a/src/main/java/org/broadinstitute/consent/http/models/DatasetSummary.java b/src/main/java/org/broadinstitute/consent/http/models/DatasetSummary.java new file mode 100644 index 0000000000..4e15d2cdb2 --- /dev/null +++ b/src/main/java/org/broadinstitute/consent/http/models/DatasetSummary.java @@ -0,0 +1,4 @@ +package org.broadinstitute.consent.http.models; + +public record DatasetSummary(Integer id, String identifier, String name) { +} diff --git a/src/main/java/org/broadinstitute/consent/http/resources/DatasetResource.java b/src/main/java/org/broadinstitute/consent/http/resources/DatasetResource.java index 38121be2c2..222c4ef662 100644 --- a/src/main/java/org/broadinstitute/consent/http/resources/DatasetResource.java +++ b/src/main/java/org/broadinstitute/consent/http/resources/DatasetResource.java @@ -44,6 +44,7 @@ import org.broadinstitute.consent.http.models.AuthUser; import org.broadinstitute.consent.http.models.DataUse; import org.broadinstitute.consent.http.models.Dataset; +import org.broadinstitute.consent.http.models.DatasetSummary; import org.broadinstitute.consent.http.models.DatasetUpdate; import org.broadinstitute.consent.http.models.Dictionary; import org.broadinstitute.consent.http.models.Study; @@ -606,6 +607,22 @@ public Response searchDatasets( } } + @GET + @Produces("application/json") + @Path("/autocomplete") + @PermitAll + public Response autocompleteDatasets( + @Auth AuthUser authUser, + @QueryParam("query") String query) { + try { + userService.findUserByEmail(authUser.getEmail()); + List datasets = datasetService.searchDatasetSummaries(query); + return Response.ok(datasets).build(); + } catch (Exception e) { + return createExceptionResponse(e); + } + } + @POST @Path("/search/index") @Consumes("application/json") diff --git a/src/main/java/org/broadinstitute/consent/http/service/DatasetService.java b/src/main/java/org/broadinstitute/consent/http/service/DatasetService.java index 2dfbea133a..22a3213b9d 100644 --- a/src/main/java/org/broadinstitute/consent/http/service/DatasetService.java +++ b/src/main/java/org/broadinstitute/consent/http/service/DatasetService.java @@ -30,6 +30,7 @@ import org.broadinstitute.consent.http.models.DataUse; import org.broadinstitute.consent.http.models.Dataset; import org.broadinstitute.consent.http.models.DatasetProperty; +import org.broadinstitute.consent.http.models.DatasetSummary; import org.broadinstitute.consent.http.models.Dictionary; import org.broadinstitute.consent.http.models.Study; import org.broadinstitute.consent.http.models.StudyConversion; @@ -310,6 +311,10 @@ public List searchDatasets(String query, AccessManagement accessManagem return datasets.stream().filter(ds -> ds.isDatasetMatch(query, accessManagement)).toList(); } + public List searchDatasetSummaries(String query) { + return datasetDAO.findDatasetSummariesByQuery(query); + } + public Dataset approveDataset(Dataset dataset, User user, Boolean approval) { Boolean currentApprovalState = dataset.getDacApproval(); Integer datasetId = dataset.getDataSetId(); diff --git a/src/main/resources/assets/api-docs.yaml b/src/main/resources/assets/api-docs.yaml index 500e119ced..5f506f8294 100644 --- a/src/main/resources/assets/api-docs.yaml +++ b/src/main/resources/assets/api-docs.yaml @@ -612,6 +612,8 @@ paths: $ref: './paths/datasetIndexById.yaml' /api/dataset/search: $ref: './paths/datasetSearch.yaml' + /api/dataset/autocomplete: + $ref: './paths/datasetSummaryAutocomplete.yaml' /api/dataset/search/index: $ref: './paths/datasetSearchIndex.yaml' /api/datasetAssociation/{datasetId}: diff --git a/src/main/resources/assets/paths/datasetSummaryAutocomplete.yaml b/src/main/resources/assets/paths/datasetSummaryAutocomplete.yaml new file mode 100644 index 0000000000..32adbf6263 --- /dev/null +++ b/src/main/resources/assets/paths/datasetSummaryAutocomplete.yaml @@ -0,0 +1,21 @@ +get: + summary: Autocomplete Datasets + description: Returns all DAC approved dataset summaries matching a search query. + parameters: + - name: query + in: query + description: Search Query + required: true + schema: + type: string + tags: + - Dataset + responses: + 200: + description: A list of Dataset Summary objects + content: + application/json: + schema: + type: array + items: + $ref: '../schemas/DatasetSummary.yaml' \ No newline at end of file diff --git a/src/main/resources/assets/schemas/DatasetSummary.yaml b/src/main/resources/assets/schemas/DatasetSummary.yaml new file mode 100644 index 0000000000..02d42c6384 --- /dev/null +++ b/src/main/resources/assets/schemas/DatasetSummary.yaml @@ -0,0 +1,11 @@ +type: object +properties: + id: + type: integer + description: The unique identifier for a dataset + identifier: + type: string + description: The public DUOS identifier for a dataset + name: + type: string + description: The dataset name diff --git a/src/test/java/org/broadinstitute/consent/http/db/DatasetDAOTest.java b/src/test/java/org/broadinstitute/consent/http/db/DatasetDAOTest.java index 4efae07e57..819d3cfe9e 100644 --- a/src/test/java/org/broadinstitute/consent/http/db/DatasetDAOTest.java +++ b/src/test/java/org/broadinstitute/consent/http/db/DatasetDAOTest.java @@ -38,6 +38,7 @@ import org.broadinstitute.consent.http.models.Dataset; import org.broadinstitute.consent.http.models.DatasetAudit; import org.broadinstitute.consent.http.models.DatasetProperty; +import org.broadinstitute.consent.http.models.DatasetSummary; import org.broadinstitute.consent.http.models.Dictionary; import org.broadinstitute.consent.http.models.Election; import org.broadinstitute.consent.http.models.FileStorageObject; @@ -1148,6 +1149,48 @@ void testFindDatasetsByCustodian() { assertNotEquals(dataset2.getDataSetId(), datasets.stream().map(Dataset::getDataSetId).toList().get(0)); } + @Test + void testFindDatasetSummariesByQuery() { + Dataset dataset = createDataset(); + Dataset dataset2 = createDataset(); + User user = createUser(); + datasetDAO.updateDatasetApproval(true, Instant.now(), user.getUserId(), dataset.getDataSetId()); + datasetDAO.updateDatasetApproval(true, Instant.now(), user.getUserId(), dataset2.getDataSetId()); + + List summaries = datasetDAO.findDatasetSummariesByQuery(dataset.getName()); + assertNotNull(summaries); + assertFalse(summaries.isEmpty()); + assertEquals(dataset.getDataSetId(), summaries.stream().map(DatasetSummary::id).toList().get(0)); + assertNotEquals(dataset2.getDataSetId(), summaries.stream().map(DatasetSummary::id).toList().get(0)); + } + + @Test + void testFindDatasetSummariesByQuery_NotApproved() { + Dataset dataset = createDataset(); + + List summaries = datasetDAO.findDatasetSummariesByQuery(dataset.getName()); + assertNotNull(summaries); + assertTrue(summaries.isEmpty()); + } + + @Test + void testFindDatasetSummariesByQuery_NullQuery() { + createDataset(); + + List summaries = datasetDAO.findDatasetSummariesByQuery(null); + assertNotNull(summaries); + assertTrue(summaries.isEmpty()); + } + + @Test + void testFindDatasetSummariesByQuery_EmptyQuery() { + createDataset(); + + List summaries = datasetDAO.findDatasetSummariesByQuery(""); + assertNotNull(summaries); + assertTrue(summaries.isEmpty()); + } + private DarCollection createDarCollectionWithDatasets(int dacId, User user, List datasets) { String darCode = "DAR-" + RandomUtils.nextInt(1, 999999); diff --git a/src/test/java/org/broadinstitute/consent/http/resources/DatasetResourceTest.java b/src/test/java/org/broadinstitute/consent/http/resources/DatasetResourceTest.java index 981e0b8118..927d7cc06b 100644 --- a/src/test/java/org/broadinstitute/consent/http/resources/DatasetResourceTest.java +++ b/src/test/java/org/broadinstitute/consent/http/resources/DatasetResourceTest.java @@ -42,6 +42,7 @@ import org.broadinstitute.consent.http.models.DataUseBuilder; import org.broadinstitute.consent.http.models.Dataset; import org.broadinstitute.consent.http.models.DatasetProperty; +import org.broadinstitute.consent.http.models.DatasetSummary; import org.broadinstitute.consent.http.models.Error; import org.broadinstitute.consent.http.models.Study; import org.broadinstitute.consent.http.models.StudyProperty; @@ -586,6 +587,18 @@ void testSearchDatasetsOpenAccess() { assertEquals(GsonUtil.buildGson().toJson(List.of(ds)), response.getEntity()); } + @Test + void testAutocompleteDatasets() { + when(authUser.getEmail()).thenReturn("testauthuser@test.com"); + when(userService.findUserByEmail("testauthuser@test.com")).thenReturn(user); + when(datasetService.searchDatasetSummaries(any())).thenReturn(List.of(new DatasetSummary(1, "ID", "Name"))); + + initResource(); + try (Response response = resource.autocompleteDatasets(authUser, "test")) { + assertTrue(HttpStatusCodes.isSuccess(response.getStatus())); + } + } + @Test void testSearchDatasetIndex() throws IOException { String query = "{ \"dataUse\": [\"HMB\"] }";