From 6b6728f0d22390ffedc3734a57ef7175bc6509dc Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:21:18 -0500 Subject: [PATCH 01/14] [BI-2047] - added getByObservationVariableDbId --- .../org/breedinginsight/services/TraitService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/breedinginsight/services/TraitService.java b/src/main/java/org/breedinginsight/services/TraitService.java index 8394acd28..2bd8eb4b3 100644 --- a/src/main/java/org/breedinginsight/services/TraitService.java +++ b/src/main/java/org/breedinginsight/services/TraitService.java @@ -491,4 +491,15 @@ public List getByName(UUID programId, List names) throws DoesNotE return traitDAO.getTraitsByTraitName(programId, names.stream().map(name -> Trait.builder().observationVariableName(name).build()).collect(Collectors.toList())); } + + public Trait getByObservationVariableDbId(UUID programId, String observationVariableDbId) throws DoesNotExistException { + if (!programService.exists(programId)) { + throw new DoesNotExistException("Program does not exist"); + } + + return traitDAO.getTraitsFullByProgramId(programId).stream() + .filter(t -> t.getObservationVariableDbId().equals(observationVariableDbId)) + .findFirst().orElseThrow(() -> new DoesNotExistException("Trait not found for observationVariableDbId: " + observationVariableDbId)); + + } } From f44a4f9610d8b934a9068552667c67553bf42f97 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:41:44 -0500 Subject: [PATCH 02/14] [BI-2047] - added observations test --- ...ObservationsControllerIntegrationTest.java | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java index b9ba07d43..8c3f058bb 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java @@ -63,6 +63,7 @@ import java.util.*; import static io.micronaut.http.HttpRequest.*; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; @MicronautTest @@ -71,8 +72,10 @@ public class BrAPIObservationsControllerIntegrationTest extends BrAPITest { private Program program; - private String experimentId; + private String experimentId; private List envIds = new ArrayList<>(); + // Use hardcoded values for more deterministic test runs and easier assertions. + private List values = List.of(0.125F, 12.415F); private final List> rows = new ArrayList<>(); private final List columns = ExperimentFileColumns.getOrderedColumns(); private List traits; @@ -170,15 +173,11 @@ void setup() throws Exception { Map row2 = makeExpImportRow("Env2"); // Add test observation data - for (Trait trait : traits) { - Random random = new Random(); - + for (int i = 0; i < traits.size(); i++) { // TODO: test for sending obs data as double. // A float is returned from the backend instead of double. there is a separate card to fix this. - // Double val1 = Math.random(); - - Float val1 = random.nextFloat(); - row1.put(trait.getObservationVariableName(), val1); + int valueIndex = i % values.size(); // Prevent overflow. + row1.put(traits.get(i).getObservationVariableName(), values.get(valueIndex)); } rows.add(row1); @@ -273,7 +272,7 @@ public void testGetObsTableOK() { } @Test - @Disabled("Disabled until fetching of observations is implemented") + @Disabled("Disabled until fetching of observations is implemented in BI-2506.") public void testGetObsListByExpId() { Flowable> call = client.exchange( GET(String.format("/programs/%s/brapi/v2/observations?trialDbId=%s", program.getId(), experimentId)) @@ -286,7 +285,7 @@ public void testGetObsListByExpId() { } @Test - @Disabled("Disabled until fetching of observations is implemented") + @Disabled("Disabled until fetching of observations is implemented in BI-2506.") public void testGetOUById() { Flowable> call = client.exchange( GET(String.format("/programs/%s/brapi/v2/observations?trialDbId=%s", program.getId(), experimentId)) @@ -311,6 +310,31 @@ public void testGetOUById() { assertEquals(HttpStatus.OK, ouResponse.getStatus()); } + @Test + public void testGetObsByStudyDbId() { + // Make a GET request to the /observations endpoint with the studyDbId query parameter. + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observations?studyDbId=%s", program.getId(), envIds.get(0))) + .bearerAuth("test-registered-user"), + String.class + ); + + // Check for 200 OK response. + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + // Check that two observations were returned. + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray observations = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, observations.size()); + + // Check the observation values, keep in mind the order of results is not guaranteed. + Float value1 = observations.get(0).getAsJsonObject().get("value").getAsFloat(); + Float value2 = observations.get(1).getAsJsonObject().get("value").getAsFloat(); + assertTrue(values.contains(value1), "Observation with value " + value1 + " not found."); + assertTrue(values.contains(value2), "Observation with value " + value2 + " not found."); + } + private File writeDataToFile(List> data, List traits) throws IOException { File file = File.createTempFile("test", ".csv"); From cfea75b728cc4b86d8ac5941eceac772465db806 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:42:25 -0500 Subject: [PATCH 03/14] [BI-2047] - implemented BrAPIObservationsController::observationsGet --- .../brapi/v2/BrAPIObservationsController.java | 77 ++++++++++++++++++- .../brapi/v2/dao/BrAPIObservationDAO.java | 28 ++++++- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index cbb476910..f1d5d3841 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -28,13 +28,17 @@ import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.client.v2.modules.phenotype.ObservationsApi; +import org.brapi.v2.model.BrAPIIndexPagination; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.BrAPIStatus; import org.brapi.v2.model.BrAPIWSMIMEDataTypes; import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.pheno.BrAPIObservation; -import org.brapi.v2.model.pheno.response.BrAPIObservationTableResponse; +import org.brapi.v2.model.pheno.response.*; import org.breedinginsight.api.auth.ProgramSecured; import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationDAO; import org.breedinginsight.brapi.v2.dao.BrAPIStudyDAO; import org.breedinginsight.brapi.v2.model.request.query.ObservationQuery; import org.breedinginsight.daos.ProgramDAO; @@ -47,6 +51,8 @@ import javax.annotation.Nullable; import javax.inject.Inject; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) @@ -57,13 +63,20 @@ public class BrAPIObservationsController { private final ProgramDAO programDAO; private final BrAPIStudyDAO brAPIStudyDAO; private final BrAPIEndpointProvider brAPIEndpointProvider; + private final BrAPIObservationDAO observationDAO; @Inject - public BrAPIObservationsController(ProgramService programService, ProgramDAO programDAO, ProgramDAO programDAO1, BrAPIStudyDAO brAPIStudyDAO, BrAPIEndpointProvider brAPIEndpointProvider) { + public BrAPIObservationsController(ProgramService programService, + ProgramDAO programDAO, + ProgramDAO programDAO1, + BrAPIStudyDAO brAPIStudyDAO, + BrAPIEndpointProvider brAPIEndpointProvider, + BrAPIObservationDAO brAPIObservationDAO) { this.programService = programService; this.programDAO = programDAO1; this.brAPIStudyDAO = brAPIStudyDAO; this.brAPIEndpointProvider = brAPIEndpointProvider; + this.observationDAO = brAPIObservationDAO; } @Get("/observations") @@ -93,8 +106,64 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, @Nullable @QueryValue("externalReferenceSource") String externalReferenceSource, @Nullable @QueryValue("page") Integer page, @Nullable @QueryValue("pageSize") Integer pageSize) { - //TODO - return HttpResponse.notFound(); + log.debug("observationsGet: fetching observations by filters"); + Optional program = programService.getById(programId); + if(program.isEmpty()) { + log.warn("Program id: " + programId + " not found"); + return HttpResponse.notFound(); + } + + try { + + // TODO: BI-2506 - implement support for all query parameters. + List unsupportedParams = Stream.of( + observationDbId, + observationUnitDbId, + observationVariableDbId, + locationDbId, + seasonDbId, + observationTimeStampRangeStart, + observationTimeStampRangeEnd, + observationUnitLevelName, + observationUnitLevelOrder, + observationUnitLevelCode, + observationUnitLevelRelationshipName, + observationUnitLevelRelationshipOrder, + observationUnitLevelRelationshipCode, + observationUnitLevelRelationshipDbId, + commonCropName, + programDbId, + trialDbId, + germplasmDbId, + externalReferenceID, + externalReferenceId, + externalReferenceSource + ).filter(Objects::nonNull).collect(Collectors.toList()); + + if (!unsupportedParams.isEmpty()) { + return HttpResponse.status(HttpStatus.NOT_IMPLEMENTED).body( + new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Unsupported query parameter. Only studyDbId, page, and pageSize are supported.")))) + ); + } + + // Handle studyDbId and pagination query params for Field Book integration. + List observations = observationDAO.getObservationsByStudyIds(List.of(studyDbId), program.get()); + + return HttpResponse.ok(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(0) + .totalPages(1) + .pageSize(observations.size()) + .totalCount(observations.size()))) + .result(new BrAPIObservationListResponseResult().data(observations))); + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), e); + return HttpResponse.serverError(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observations"))))); + } catch (Exception e) { + log.error("Error fetching Observations", e); + return HttpResponse.serverError(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observations"))))); + } } @Get("/observations/{observationDbId}") diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index fdfbd18a1..df6b37865 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -38,7 +38,10 @@ import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.cache.ProgramCacheProvider; import org.breedinginsight.model.Program; +import org.breedinginsight.model.Trait; +import org.breedinginsight.services.TraitService; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.BrAPIDAOUtil; import org.breedinginsight.utilities.Utilities; import org.jetbrains.annotations.NotNull; @@ -62,6 +65,7 @@ public class BrAPIObservationDAO extends BrAPICachedDAO { private final BrAPIEndpointProvider brAPIEndpointProvider; private final String referenceSource; private boolean runScheduledTasks; + private final TraitService traitService; @Inject public BrAPIObservationDAO(ProgramDAO programDAO, @@ -71,7 +75,7 @@ public BrAPIObservationDAO(ProgramDAO programDAO, BrAPIEndpointProvider brAPIEndpointProvider, @Property(name = "brapi.server.reference-source") String referenceSource, @Property(name = "micronaut.bi.api.run-scheduled-tasks") boolean runScheduledTasks, - ProgramCacheProvider programCacheProvider) { + ProgramCacheProvider programCacheProvider, TraitService traitService) { this.programDAO = programDAO; this.importDAO = importDAO; this.observationUnitDAO = observationUnitDAO; @@ -79,6 +83,7 @@ public BrAPIObservationDAO(ProgramDAO programDAO, this.brAPIEndpointProvider = brAPIEndpointProvider; this.referenceSource = referenceSource; this.runScheduledTasks = runScheduledTasks; + this.traitService = traitService; this.programCache = programCacheProvider.getProgramCache(this::fetchProgramObservations, BrAPIObservation.class); } @@ -242,6 +247,27 @@ public List getObservationsByObservationUnitsAndStudies(Collec .collect(Collectors.toList()); } + public List getObservationsByStudyIds(Collection studyDbIds, Program program) throws ApiException { + if(studyDbIds.isEmpty()) { + return Collections.emptyList(); + } + String xrefSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); + // Lookup studyDbId + return getProgramObservations(program.getId()).values().stream() + .filter(o -> { + Optional xref = Utilities.getExternalReference(o.getExternalReferences(), xrefSource); + return xref.filter(brAPIExternalReference -> studyDbIds.contains(brAPIExternalReference.getReferenceId())).isPresent(); + }).peek(o -> { + try { + Trait trait = traitService.getByObservationVariableDbId(program.getId(), o.getObservationVariableDbId()); + o.setObservationVariableDbId(trait.getId().toString()); + } catch (DoesNotExistException e) { + throw new RuntimeException(e); + } + + }).collect(Collectors.toList()); + } + @NotNull private ApiResponse, Optional>> searchObservationsSearchResultsDbIdGet(UUID programId, String searchResultsDbId, Integer page, Integer pageSize) throws ApiException { ObservationsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationsApi.class); From c83d037d20b698a5f5cfe837bbe9902d83ee6779 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:06:59 -0500 Subject: [PATCH 04/14] [BI-2047] - improved efficiency --- .../brapi/v2/dao/BrAPIObservationDAO.java | 19 +++++++++---------- .../services/TraitService.java | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index df6b37865..ab096a0ea 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -247,25 +247,24 @@ public List getObservationsByObservationUnitsAndStudies(Collec .collect(Collectors.toList()); } - public List getObservationsByStudyIds(Collection studyDbIds, Program program) throws ApiException { + public List getObservationsByStudyIds(Collection studyDbIds, Program program) throws ApiException, DoesNotExistException { if(studyDbIds.isEmpty()) { return Collections.emptyList(); } String xrefSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); + // Get all observations for the program. + Collection observations = getProgramObservations(program.getId()).values(); + // Build a hashmap of traits for fast lookup. The key is ObservationVariableDbId, the value is the Trait Id. + HashMap traitIdsByObservationVariableDbId = traitService.getIdsByObservationVariableDbIds(program.getId(), observations.stream().map(BrAPIObservation::getObservationVariableDbId).collect(Collectors.toList())); + // Lookup studyDbId return getProgramObservations(program.getId()).values().stream() .filter(o -> { Optional xref = Utilities.getExternalReference(o.getExternalReferences(), xrefSource); return xref.filter(brAPIExternalReference -> studyDbIds.contains(brAPIExternalReference.getReferenceId())).isPresent(); - }).peek(o -> { - try { - Trait trait = traitService.getByObservationVariableDbId(program.getId(), o.getObservationVariableDbId()); - o.setObservationVariableDbId(trait.getId().toString()); - } catch (DoesNotExistException e) { - throw new RuntimeException(e); - } - - }).collect(Collectors.toList()); + }) + .peek(o -> o.setObservationVariableDbId(traitIdsByObservationVariableDbId.get(o.getObservationVariableDbId()))) + .collect(Collectors.toList()); } @NotNull diff --git a/src/main/java/org/breedinginsight/services/TraitService.java b/src/main/java/org/breedinginsight/services/TraitService.java index 2bd8eb4b3..cdedac3ba 100644 --- a/src/main/java/org/breedinginsight/services/TraitService.java +++ b/src/main/java/org/breedinginsight/services/TraitService.java @@ -502,4 +502,18 @@ public Trait getByObservationVariableDbId(UUID programId, String observationVari .findFirst().orElseThrow(() -> new DoesNotExistException("Trait not found for observationVariableDbId: " + observationVariableDbId)); } + + public HashMap getIdsByObservationVariableDbIds(UUID programId, List observationVariableDbIds) throws DoesNotExistException { + if (!programService.exists(programId)) { + throw new DoesNotExistException("Program does not exist"); + } + return traitDAO.getTraitsFullByProgramId(programId).stream() + .filter(t -> observationVariableDbIds.contains(t.getObservationVariableDbId())) + .collect(Collectors.toMap( + Trait::getObservationVariableDbId, + (t) -> t.getId().toString(), + (existing, replacement) -> existing, + HashMap::new + )); + } } From e3be5b61313d85c9e243f633b0173da2e585103c Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:23:53 -0500 Subject: [PATCH 05/14] [BI-2047] - implemented pagination --- .../brapi/v2/BrAPIObservationsController.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index f1d5d3841..71a7d9eea 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -147,14 +147,43 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, ); } - // Handle studyDbId and pagination query params for Field Book integration. - List observations = observationDAO.getObservationsByStudyIds(List.of(studyDbId), program.get()); + // Get a filtered list of observations. + List observations = observationDAO.getObservationsByFilters(program.get(), studyDbId); - return HttpResponse.ok(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(0) - .totalPages(1) + // Handle pagination query params. + int totalCount = observations.size(); // Total number of records in the unpaged super set. + int actualPage = page != null ? page : 0; // Zero-indexed page. + int requestedPageSize = pageSize != null ? Math.min(pageSize, totalCount) : totalCount; // The lesser of pageSize and totalCount. + int totalPages = totalCount / requestedPageSize + ((totalCount % requestedPageSize == 0) ? 0 : 1); // Integer division and round up. + log.info("(Pagination) totalCount: " + totalCount + " actualPage (0-indexed): " + actualPage + " requestedPageSize: " + requestedPageSize + " totalPages: " + totalPages); + + // Determine validity of pagination query parameters. + boolean pageSizeValid = pageSize != null && pageSize > 0 && pageSize <= totalCount; + boolean pageValid = page != null && page >= 0 && page < totalPages; + + // Only paginate if valid pagination values were sent. + if (pageSizeValid && pageValid) { + int start = actualPage * requestedPageSize; + // Account for last page, which may have fewer than requestedPageSize items, or exactly requestedPageSize items. + int end = (actualPage == (totalPages - 1) && totalCount % requestedPageSize != 0) ? (start + (totalCount % requestedPageSize)) : Math.min(((actualPage + 1) * requestedPageSize), totalCount); + log.info("(Pagination) start " + start + " end " + end); + // Sort observations so that paging is consistent and coherent. + observations.sort(Comparator.comparing(BrAPIObservation::getObservationDbId)); + // Paginate response. + observations = observations.subList(start, end); + } else if (pageSize != null || page != null) { + // If one or more of the pagination query parameters are not null, both must be present and valid. + String errorMessage = "Invalid query parameters: page, pageSize"; + return HttpResponse.badRequest(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message(errorMessage))))); + } + + return HttpResponse.ok(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(actualPage) + .totalPages(totalPages) .pageSize(observations.size()) - .totalCount(observations.size()))) + .totalCount(totalCount))) .result(new BrAPIObservationListResponseResult().data(observations))); + } catch (ApiException e) { log.error(Utilities.generateApiExceptionLogMessage(e), e); return HttpResponse.serverError(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) From 123e94fe6ce82d79cef07414324cc056ce7bfb94 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:29:36 -0500 Subject: [PATCH 06/14] [BI-2047] - translated DbIds --- .../brapi/v2/BrAPIObservationsController.java | 2 +- .../brapi/v2/dao/BrAPIObservationDAO.java | 30 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index 71a7d9eea..fe96f523f 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -183,7 +183,7 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, .pageSize(observations.size()) .totalCount(totalCount))) .result(new BrAPIObservationListResponseResult().data(observations))); - + } catch (ApiException e) { log.error(Utilities.generateApiExceptionLogMessage(e), e); return HttpResponse.serverError(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index ab096a0ea..82f168dc1 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -247,11 +247,13 @@ public List getObservationsByObservationUnitsAndStudies(Collec .collect(Collectors.toList()); } - public List getObservationsByStudyIds(Collection studyDbIds, Program program) throws ApiException, DoesNotExistException { - if(studyDbIds.isEmpty()) { - return Collections.emptyList(); - } - String xrefSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); + // TODO: implement other filters. + public List getObservationsByFilters(Program program, String studyDbId) throws ApiException, DoesNotExistException { + + String studySource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); + String observationUnitSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.OBSERVATION_UNITS); + String observationSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.OBSERVATIONS); + // Get all observations for the program. Collection observations = getProgramObservations(program.getId()).values(); // Build a hashmap of traits for fast lookup. The key is ObservationVariableDbId, the value is the Trait Id. @@ -260,11 +262,21 @@ public List getObservationsByStudyIds(Collection study // Lookup studyDbId return getProgramObservations(program.getId()).values().stream() .filter(o -> { - Optional xref = Utilities.getExternalReference(o.getExternalReferences(), xrefSource); - return xref.filter(brAPIExternalReference -> studyDbIds.contains(brAPIExternalReference.getReferenceId())).isPresent(); + // Short circuit if filter is null. + if (studyDbId == null) return true; + Optional xref = Utilities.getExternalReference(o.getExternalReferences(), studySource); + return xref.filter(brAPIExternalReference -> studyDbId.equals(brAPIExternalReference.getReferenceId())).isPresent(); }) - .peek(o -> o.setObservationVariableDbId(traitIdsByObservationVariableDbId.get(o.getObservationVariableDbId()))) - .collect(Collectors.toList()); + .peek(o -> { + // TODO: avoid NPEs! + // Translate ObservationVariableDbId. + o.setObservationVariableDbId(traitIdsByObservationVariableDbId.get(o.getObservationVariableDbId())); + // Translate ObservationUnitDbId. + o.setObservationUnitDbId(Utilities.getExternalReference(o.getExternalReferences(), observationUnitSource).get().getReferenceId()); + // Translate ObservationId. + o.setObservationDbId(Utilities.getExternalReference(o.getExternalReferences(), observationSource).get().getReferenceId()); + // TODO: do we need to translate germplasmDbId? + }).collect(Collectors.toList()); } @NotNull From 5a0b69e9f66c31f1531c7878979ae1d5a43d12e4 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:46:14 -0500 Subject: [PATCH 07/14] [BI-2047] - handled division by zero edge case --- .../brapi/v2/BrAPIObservationsController.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index fe96f523f..5f8a4b39c 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -150,11 +150,14 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, // Get a filtered list of observations. List observations = observationDAO.getObservationsByFilters(program.get(), studyDbId); - // Handle pagination query params. - int totalCount = observations.size(); // Total number of records in the unpaged super set. - int actualPage = page != null ? page : 0; // Zero-indexed page. - int requestedPageSize = pageSize != null ? Math.min(pageSize, totalCount) : totalCount; // The lesser of pageSize and totalCount. - int totalPages = totalCount / requestedPageSize + ((totalCount % requestedPageSize == 0) ? 0 : 1); // Integer division and round up. + // Total number of records in the unpaged super set. + int totalCount = observations.size(); + // Zero-indexed page, default to zero. + int actualPage = page != null ? page : 0; + // The least of pageSize and totalCount, unless pageSize is null or zero, in which case use totalCount. + int requestedPageSize = (pageSize != null && pageSize > 0) ? Math.min(pageSize, totalCount) : totalCount; + // Integer division and round up. + int totalPages = totalCount / requestedPageSize + ((totalCount % requestedPageSize == 0) ? 0 : 1); log.info("(Pagination) totalCount: " + totalCount + " actualPage (0-indexed): " + actualPage + " requestedPageSize: " + requestedPageSize + " totalPages: " + totalPages); // Determine validity of pagination query parameters. From d0ab4c3668f4b1577375c9b7df4dfe8b951e04fd Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:58:58 -0500 Subject: [PATCH 08/14] [BI-2047] - added pagination test --- .../brapi/v2/BrAPIObservationsController.java | 2 +- ...ObservationsControllerIntegrationTest.java | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index 5f8a4b39c..4c7127528 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -161,7 +161,7 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, log.info("(Pagination) totalCount: " + totalCount + " actualPage (0-indexed): " + actualPage + " requestedPageSize: " + requestedPageSize + " totalPages: " + totalPages); // Determine validity of pagination query parameters. - boolean pageSizeValid = pageSize != null && pageSize > 0 && pageSize <= totalCount; + boolean pageSizeValid = pageSize != null && pageSize > 0; boolean pageValid = page != null && page >= 0 && page < totalPages; // Only paginate if valid pagination values were sent. diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java index 8c3f058bb..5ed4ac064 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java @@ -178,6 +178,7 @@ void setup() throws Exception { // A float is returned from the backend instead of double. there is a separate card to fix this. int valueIndex = i % values.size(); // Prevent overflow. row1.put(traits.get(i).getObservationVariableName(), values.get(valueIndex)); + row2.put(traits.get(i).getObservationVariableName(), values.get(valueIndex)); } rows.add(row1); @@ -335,6 +336,71 @@ public void testGetObsByStudyDbId() { assertTrue(values.contains(value2), "Observation with value " + value2 + " not found."); } + @Test + public void testGetObsPagination() { + + // Check no pagination. + checkPagination(null, null, HttpStatus.OK, 4, 4, 1); + // Check valid pagination, including last page edge case. + checkPagination(0, 1, HttpStatus.OK, 1, 4, 4); + checkPagination(1, 2, HttpStatus.OK, 2, 4, 2); + checkPagination(0, 3, HttpStatus.OK, 3, 4, 2); + checkPagination(1, 3, HttpStatus.OK, 1, 4, 2); + checkPagination(0, 100, HttpStatus.OK, 4, 4, 1); + // Check invalid pagination. + checkPagination(2, 2, HttpStatus.BAD_REQUEST, null, null, null); + checkPagination(0, 0, HttpStatus.BAD_REQUEST, null, null, null); + checkPagination(1, 0, HttpStatus.BAD_REQUEST, null, null, null); + checkPagination(10, 100, HttpStatus.BAD_REQUEST, null, null, null); + + } + + private void checkPagination(Integer page, Integer pageSize, HttpStatus expectedStatus, Integer expectedSize, Integer expectedTotalCount, Integer expectedTotalPages) { + // Build request URL. + String requestURL = String.format("/programs/%s/brapi/v2/observations", program.getId()); + if (page != null) { + requestURL = requestURL + "?page=" + page; + } else { + page = 0; + } + if (pageSize != null) { + requestURL = requestURL + "&pageSize=" + pageSize; + } + + // Make a GET request to the /observations endpoint with the supplied pagination parameters. + Flowable> call = client.exchange( + GET(requestURL).bearerAuth("test-registered-user"), + String.class + ); + + // Check for expected response. + try { + HttpResponse response = call.blockingFirst(); + assertEquals(expectedStatus, response.getStatus()); + + // If call.blockingFirst() doesn't throw, expect a 200 OK. + assertEquals(HttpStatus.OK, response.getStatus()); + + // Parse and check body and metadata. + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + // Get metadata. + JsonObject pagination = responseObj.getAsJsonObject("metadata").getAsJsonObject("pagination"); + // TODO: Per BrAPI docs, pageSize (metadata) should always be the + assertEquals(expectedSize, pagination.get("pageSize").getAsInt()); // TODO: check... BJTS does something different. + assertEquals(page, pagination.get("currentPage").getAsInt()); + assertEquals(expectedTotalPages, pagination.get("totalPages").getAsInt()); + assertEquals(expectedTotalCount, pagination.get("totalCount").getAsInt()); + // Get observations. + JsonArray observations = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(expectedSize, observations.size()); + + } catch (HttpClientResponseException e) { + // call.blockingFirst() will throw in the case of a non-200 code. + assertEquals(expectedStatus, e.getStatus()); + } + + } + private File writeDataToFile(List> data, List traits) throws IOException { File file = File.createTempFile("test", ".csv"); From 618d8a7358a1ec4708bb1c145e23f78e885509a2 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:10:51 -0500 Subject: [PATCH 09/14] [BI-2047] - updated comments --- .../brapi/v2/BrAPIObservationsControllerIntegrationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java index 5ed4ac064..12269df3e 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java @@ -385,8 +385,7 @@ private void checkPagination(Integer page, Integer pageSize, HttpStatus expected JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); // Get metadata. JsonObject pagination = responseObj.getAsJsonObject("metadata").getAsJsonObject("pagination"); - // TODO: Per BrAPI docs, pageSize (metadata) should always be the - assertEquals(expectedSize, pagination.get("pageSize").getAsInt()); // TODO: check... BJTS does something different. + assertEquals(expectedSize, pagination.get("pageSize").getAsInt()); assertEquals(page, pagination.get("currentPage").getAsInt()); assertEquals(expectedTotalPages, pagination.get("totalPages").getAsInt()); assertEquals(expectedTotalCount, pagination.get("totalCount").getAsInt()); From 9ea73d45767489a2f50ce3b5e350e5bd316e90f9 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Mon, 17 Feb 2025 14:12:06 -0500 Subject: [PATCH 10/14] [BI-2047] - improved exception handling --- .../brapi/v2/dao/BrAPIObservationDAO.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index 82f168dc1..ced857c9d 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -247,7 +247,7 @@ public List getObservationsByObservationUnitsAndStudies(Collec .collect(Collectors.toList()); } - // TODO: implement other filters. + // TODO: implement other filters in BI-2506. public List getObservationsByFilters(Program program, String studyDbId) throws ApiException, DoesNotExistException { String studySource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); @@ -268,14 +268,15 @@ public List getObservationsByFilters(Program program, String s return xref.filter(brAPIExternalReference -> studyDbId.equals(brAPIExternalReference.getReferenceId())).isPresent(); }) .peek(o -> { - // TODO: avoid NPEs! // Translate ObservationVariableDbId. o.setObservationVariableDbId(traitIdsByObservationVariableDbId.get(o.getObservationVariableDbId())); // Translate ObservationUnitDbId. - o.setObservationUnitDbId(Utilities.getExternalReference(o.getExternalReferences(), observationUnitSource).get().getReferenceId()); + o.setObservationUnitDbId(Utilities.getExternalReference(o.getExternalReferences(), observationUnitSource) + .orElseThrow(() -> new RuntimeException("observationUnit xref not found on observation")).getReferenceId()); // Translate ObservationId. - o.setObservationDbId(Utilities.getExternalReference(o.getExternalReferences(), observationSource).get().getReferenceId()); - // TODO: do we need to translate germplasmDbId? + o.setObservationDbId(Utilities.getExternalReference(o.getExternalReferences(), observationSource) + .orElseThrow(() -> new RuntimeException("observation xref not found on observation")).getReferenceId()); + // TODO: consider translating germplasmDbId in BI-2506. }).collect(Collectors.toList()); } From 3279b5befecc68545e102ff8b1902ca8797ee141 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:43:35 -0500 Subject: [PATCH 11/14] [BI-2047] - reused observations list --- .../org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index ced857c9d..f68024184 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -260,7 +260,7 @@ public List getObservationsByFilters(Program program, String s HashMap traitIdsByObservationVariableDbId = traitService.getIdsByObservationVariableDbIds(program.getId(), observations.stream().map(BrAPIObservation::getObservationVariableDbId).collect(Collectors.toList())); // Lookup studyDbId - return getProgramObservations(program.getId()).values().stream() + return observations.stream() .filter(o -> { // Short circuit if filter is null. if (studyDbId == null) return true; From edb94f5234e8eb7a1c7b4f25096fec283cdaba39 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:28:27 -0500 Subject: [PATCH 12/14] [BI-2047] - added correct defaults --- .../brapi/v2/BrAPIObservationsController.java | 26 +++++++++++-------- .../brapi/v2/dao/BrAPIObservationDAO.java | 2 +- ...ObservationsControllerIntegrationTest.java | 12 ++++++--- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index 4c7127528..4611fd34f 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -150,38 +150,42 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, // Get a filtered list of observations. List observations = observationDAO.getObservationsByFilters(program.get(), studyDbId); + // If page is not provided, set it to the default value 0. + if (page == null) page = 0; + // If pageSize is not provided, set it to the default value 1000. + if (pageSize == null) pageSize = 1000; + // Total number of records in the unpaged super set. int totalCount = observations.size(); - // Zero-indexed page, default to zero. - int actualPage = page != null ? page : 0; - // The least of pageSize and totalCount, unless pageSize is null or zero, in which case use totalCount. - int requestedPageSize = (pageSize != null && pageSize > 0) ? Math.min(pageSize, totalCount) : totalCount; + // The least of pageSize and totalCount, unless pageSize is zero, in which case use totalCount. + int requestedPageSize = pageSize > 0 ? Math.min(pageSize, totalCount) : totalCount; // Integer division and round up. int totalPages = totalCount / requestedPageSize + ((totalCount % requestedPageSize == 0) ? 0 : 1); - log.info("(Pagination) totalCount: " + totalCount + " actualPage (0-indexed): " + actualPage + " requestedPageSize: " + requestedPageSize + " totalPages: " + totalPages); + log.info("(Pagination) totalCount: " + totalCount + " page (0-indexed): " + page + " requestedPageSize: " + requestedPageSize + " totalPages: " + totalPages); // Determine validity of pagination query parameters. - boolean pageSizeValid = pageSize != null && pageSize > 0; - boolean pageValid = page != null && page >= 0 && page < totalPages; + boolean pageSizeValid = pageSize > 0; + boolean pageValid = page >= 0 && page < totalPages; // Only paginate if valid pagination values were sent. if (pageSizeValid && pageValid) { - int start = actualPage * requestedPageSize; + int start = page * requestedPageSize; // Account for last page, which may have fewer than requestedPageSize items, or exactly requestedPageSize items. - int end = (actualPage == (totalPages - 1) && totalCount % requestedPageSize != 0) ? (start + (totalCount % requestedPageSize)) : Math.min(((actualPage + 1) * requestedPageSize), totalCount); + int end = (page == (totalPages - 1) && totalCount % requestedPageSize != 0) ? (start + (totalCount % requestedPageSize)) : Math.min(((page + 1) * requestedPageSize), totalCount); log.info("(Pagination) start " + start + " end " + end); // Sort observations so that paging is consistent and coherent. observations.sort(Comparator.comparing(BrAPIObservation::getObservationDbId)); // Paginate response. observations = observations.subList(start, end); - } else if (pageSize != null || page != null) { + } else { // If one or more of the pagination query parameters are not null, both must be present and valid. String errorMessage = "Invalid query parameters: page, pageSize"; return HttpResponse.badRequest(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) .message(errorMessage))))); } - return HttpResponse.ok(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(actualPage) + return HttpResponse.ok(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination() + .currentPage(page) .totalPages(totalPages) .pageSize(observations.size()) .totalCount(totalCount))) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index f68024184..e9d2f788f 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -259,7 +259,7 @@ public List getObservationsByFilters(Program program, String s // Build a hashmap of traits for fast lookup. The key is ObservationVariableDbId, the value is the Trait Id. HashMap traitIdsByObservationVariableDbId = traitService.getIdsByObservationVariableDbIds(program.getId(), observations.stream().map(BrAPIObservation::getObservationVariableDbId).collect(Collectors.toList())); - // Lookup studyDbId + // Lookup studyDbId. return observations.stream() .filter(o -> { // Short circuit if filter is null. diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java index 12269df3e..27cf6defd 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java @@ -341,6 +341,9 @@ public void testGetObsPagination() { // Check no pagination. checkPagination(null, null, HttpStatus.OK, 4, 4, 1); + // Check page and pageSize defaults. + checkPagination(null, 2, HttpStatus.OK, 2, 4, 2); + checkPagination(0, null, HttpStatus.OK, 4, 4, 1); // Check valid pagination, including last page edge case. checkPagination(0, 1, HttpStatus.OK, 1, 4, 4); checkPagination(1, 2, HttpStatus.OK, 2, 4, 2); @@ -360,10 +363,10 @@ private void checkPagination(Integer page, Integer pageSize, HttpStatus expected String requestURL = String.format("/programs/%s/brapi/v2/observations", program.getId()); if (page != null) { requestURL = requestURL + "?page=" + page; - } else { - page = 0; } - if (pageSize != null) { + if (pageSize != null && page == null) { + requestURL = requestURL + "?pageSize=" + pageSize; + } else if (pageSize != null) { requestURL = requestURL + "&pageSize=" + pageSize; } @@ -386,7 +389,8 @@ private void checkPagination(Integer page, Integer pageSize, HttpStatus expected // Get metadata. JsonObject pagination = responseObj.getAsJsonObject("metadata").getAsJsonObject("pagination"); assertEquals(expectedSize, pagination.get("pageSize").getAsInt()); - assertEquals(page, pagination.get("currentPage").getAsInt()); + int expectedPage = page == null ? 0 : page; + assertEquals(expectedPage, pagination.get("currentPage").getAsInt()); assertEquals(expectedTotalPages, pagination.get("totalPages").getAsInt()); assertEquals(expectedTotalCount, pagination.get("totalCount").getAsInt()); // Get observations. From c988d88475794ea38ac27b7ecc6f3d5959075509 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:21:15 -0500 Subject: [PATCH 13/14] [BI-2047] - removed comment --- .../breedinginsight/brapi/v2/BrAPIObservationsController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java index 4611fd34f..27ac4e34c 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -178,7 +178,6 @@ public HttpResponse observationsGet(@PathVariable("programId") UUID programId, // Paginate response. observations = observations.subList(start, end); } else { - // If one or more of the pagination query parameters are not null, both must be present and valid. String errorMessage = "Invalid query parameters: page, pageSize"; return HttpResponse.badRequest(new BrAPIObservationListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) .message(errorMessage))))); From 8d41a91fb9cd1309e7370b9e01163e6ce27c9d08 Mon Sep 17 00:00:00 2001 From: mlm483 <128052931+mlm483@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:59:44 -0500 Subject: [PATCH 14/14] [BI-2047] - added studyDbId translation --- .../org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index e9d2f788f..d0217748a 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -276,6 +276,9 @@ public List getObservationsByFilters(Program program, String s // Translate ObservationId. o.setObservationDbId(Utilities.getExternalReference(o.getExternalReferences(), observationSource) .orElseThrow(() -> new RuntimeException("observation xref not found on observation")).getReferenceId()); + // Translate StudyDbId. + o.setStudyDbId(Utilities.getExternalReference(o.getExternalReferences(), studySource) + .orElseThrow(() -> new RuntimeException("study xref not found on observation")).getReferenceId()); // TODO: consider translating germplasmDbId in BI-2506. }).collect(Collectors.toList()); }