From 47f808ad4babb47810d22700e5299a9903b7146d Mon Sep 17 00:00:00 2001 From: rob-ouser-bi Date: Fri, 29 May 2026 14:21:48 +0000 Subject: [PATCH 1/6] [autocommit] bumping build number --- src/main/resources/version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index e5bfb2383..471ad6749 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -14,5 +14,5 @@ # limitations under the License. # -version=v1.4.0+1147 -versionInfo=https://github.com/Breeding-Insight/bi-api/commit/c81a0e4717287a7ceb51c667034ca945af14ac09 +version=v1.4.0+1159 +versionInfo=https://github.com/Breeding-Insight/bi-api/commit/1a4143c71cce5e5344dba867af421f9010d1adb1 From 9f8b35e0b5d28a5db4a8d97d4d8c429b93976dd4 Mon Sep 17 00:00:00 2001 From: Keerthi Humsika Kattamudi Date: Fri, 5 Jun 2026 10:27:39 -0500 Subject: [PATCH 2/6] BI-2848: Committing initial code changes. --- .../geno/GenotypeDataUploadController.java | 37 ++- .../model/GenotypeImportDetails.java | 30 ++ .../services/geno/GenotypeService.java | 4 + .../geno/impl/GigwaGenotypeServiceImpl.java | 259 +++++++++++------- .../mappers/GenotypeImportQueryMapper.java | 36 +++ .../V1.36.0__create_genotype_import_table.sql | 55 ++++ 6 files changed, 313 insertions(+), 108 deletions(-) create mode 100644 src/main/java/org/breedinginsight/model/GenotypeImportDetails.java create mode 100644 src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java create mode 100644 src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql diff --git a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java index 5f8540cae..9b698dbcd 100644 --- a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java @@ -6,18 +6,25 @@ import io.micronaut.http.multipart.CompletedFileUpload; import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; -import org.breedinginsight.api.auth.AuthenticatedUser; -import org.breedinginsight.api.auth.ProgramSecured; -import org.breedinginsight.api.auth.ProgramSecuredRole; -import org.breedinginsight.api.auth.SecurityService; +import org.breedinginsight.api.auth.*; +import org.breedinginsight.api.model.v1.request.query.QueryParams; +import org.breedinginsight.api.model.v1.response.DataResponse; import org.breedinginsight.api.model.v1.response.Response; +import org.breedinginsight.api.model.v1.validators.QueryValid; import org.breedinginsight.api.v1.controller.metadata.AddMetadata; import org.breedinginsight.brapps.importer.model.response.ImportResponse; +import org.breedinginsight.model.GenotypeImportDetails; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.exceptions.AuthorizationException; import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.geno.GenotypeService; +import org.breedinginsight.utilities.response.ResponseUtils; +import org.breedinginsight.utilities.response.mappers.GenotypeImportQueryMapper; import javax.inject.Inject; +import javax.validation.Valid; +import java.util.Optional; import java.util.UUID; @Slf4j @@ -25,11 +32,31 @@ public class GenotypeDataUploadController { private final GenotypeService genoService; private final SecurityService securityService; + private final ProgramService programService; + private final GenotypeImportQueryMapper genotypeImportQueryMapper; @Inject - public GenotypeDataUploadController(GenotypeService genoService, SecurityService securityService) { + public GenotypeDataUploadController(GenotypeService genoService, SecurityService securityService, + ProgramService programService, GenotypeImportQueryMapper genotypeImportQueryMapper) { this.genoService = genoService; this.securityService = securityService; + this.programService = programService; + this.genotypeImportQueryMapper = genotypeImportQueryMapper; + } + + @Get("programs/{programId}/geno/imports{?queryParams*}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES) + public HttpResponse>> getGenotypeImports( + @PathVariable UUID programId, + @QueryValue @QueryValid(using = GenotypeImportQueryMapper.class) @Valid QueryParams queryParams) { + Optional program = programService.getById(programId); + if (program.isEmpty()) { + log.info("programId not found: {}", programId.toString()); + return HttpResponse.notFound(); + } + + return ResponseUtils.getQueryResponse(genoService.getGenotypeImports(programId), genotypeImportQueryMapper, queryParams); } @Post("programs/{programId}/submissions/{submissionId}/geno/import") diff --git a/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java new file mode 100644 index 000000000..8f3282cae --- /dev/null +++ b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java @@ -0,0 +1,30 @@ +package org.breedinginsight.model; + +import io.micronaut.core.annotation.Introspected; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +import java.time.OffsetDateTime; +import java.util.UUID; + +@Getter +@Setter +@Accessors(chain = true) +@ToString +@SuperBuilder +@NoArgsConstructor +@Introspected +@Jacksonized +public class GenotypeImportDetails { + private UUID sampleSubmissionId; + private String projectNameForSampleSubmission; + private String sampleSubmissionCreatedBy; + private String genotypingFileName; + private OffsetDateTime genotypingImportDate; + private String genotypingImportBy; +} diff --git a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java index 7da2f1fbd..ed1d905ba 100644 --- a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java +++ b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java @@ -7,11 +7,15 @@ import org.breedinginsight.model.GermplasmGenotype; import org.breedinginsight.services.exceptions.AuthorizationException; import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.model.GenotypeImportDetails; +import java.util.List; import java.util.UUID; public interface GenotypeService { ImportResponse submitGenotypeData(UUID userId, UUID programId, UUID submissionId, CompletedFileUpload uploadedFile) throws DoesNotExistException, AuthorizationException, ApiException; GermplasmGenotype retrieveGenotypeData(UUID programId, BrAPIGermplasm germplasm) throws DoesNotExistException, AuthorizationException, ApiException; + + List getGenotypeImports(UUID programId); } diff --git a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java index dbc7e89ad..b2b5f05f3 100644 --- a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java +++ b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java @@ -44,6 +44,7 @@ import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; +import org.breedinginsight.dao.db.tables.BiUserTable; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.SampleSubmissionDAO; import org.breedinginsight.daos.UserDAO; @@ -74,6 +75,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import org.breedinginsight.model.GenotypeImportDetails; +import static org.breedinginsight.dao.db.Tables.BI_USER; +import static org.breedinginsight.dao.db.Tables.GENOTYPE_IMPORT; +import static org.breedinginsight.dao.db.Tables.IMPORTER_IMPORT; +import static org.breedinginsight.dao.db.Tables.SAMPLE_SUBMISSION; + @Singleton @Slf4j public class GigwaGenotypeServiceImpl implements GenotypeService { @@ -153,36 +160,38 @@ public ImportResponse submitGenotypeData(UUID userId, UUID programId, UUID submi Program program = getProgram(programId); User user = userDAO.getUser(userId) - .orElseThrow(() -> new DoesNotExistException("User ID does not exist")); + .orElseThrow(() -> new DoesNotExistException("User ID does not exist")); ImportMapping mapping = importMappingDAO.getSystemMappingByName("GenotypicDataImport") - .get(0); + .get(0); ImportUpload upload; ImportProgress progress = ImportProgress.builder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedAt(OffsetDateTime.now()) - .updatedBy(userId) - .statuscode((short) HttpStatus.ACCEPTED.getCode()) - .message("Validating file") - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedAt(OffsetDateTime.now()) + .updatedBy(userId) + .statuscode((short) HttpStatus.ACCEPTED.getCode()) + .message("Validating file") + .build(); try { upload = dsl.transactionResult(configuration -> { importDAO.createProgress(progress); ImportUpload importUpload = ImportUpload.uploadBuilder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedBy(user.getId()) - .updatedAt(OffsetDateTime.now()) - .programId(programId) - .importerProgressId(progress.getId()) - .importerMappingId(mapping.getId()) - .userId(user.getId()) - .uploadFileName(uploadedFile.getFilename()) - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedBy(user.getId()) + .updatedAt(OffsetDateTime.now()) + .programId(programId) + .importerProgressId(progress.getId()) + .importerMappingId(mapping.getId()) + .userId(user.getId()) + .uploadFileName(uploadedFile.getFilename()) + .build(); importDAO.insert(importUpload); + //logic to add record to the new JOIN table + createGenotypeImportLink(submissionId, importUpload.getId(), user.getId()); return importUpload; }); @@ -206,7 +215,7 @@ public ImportResponse submitGenotypeData(UUID userId, UUID programId, UUID submi try { byte[] fileContents = uploadedFile.getBytes(); - if(validateSamples(program, submissionId, fileContents, upload)) { + if (validateSamples(program, submissionId, fileContents, upload)) { executor.execute(() -> { try { processSubmission(gigwaAuthToken, program, submissionId, fileContents, uploadedFile.getFilename(), upload, progress); @@ -235,13 +244,13 @@ public GermplasmGenotype retrieveGenotypeData(UUID programId, BrAPIGermplasm ger BrAPIClient brAPIClient = programDAO.getCoreClient(programId); brAPIClient.setBasePath(gigwaHost + GIGWA_BRAPI_BASE_PATH); Authentication authorizationToken = brAPIClient.getAuthentication("AuthorizationToken"); - if(authorizationToken instanceof OAuth) { - ((OAuth)authorizationToken).setAccessToken(getAuthToken()); + if (authorizationToken instanceof OAuth) { + ((OAuth) authorizationToken).setAccessToken(getAuthToken()); } BrAPIClient brapiPhenoClient = programDAO.getPhenoClient(programId); - if(verifyProgramExists(brAPIClient, program)) { + if (verifyProgramExists(brAPIClient, program)) { List germplasmOUs = fetchObservationUnits(brapiPhenoClient, germplasm); List germplasmSamples = fetchSamples(brAPIClient, program, germplasmOUs); @@ -253,23 +262,66 @@ public GermplasmGenotype retrieveGenotypeData(UUID programId, BrAPIGermplasm ger List variants = fetchVariants(brAPIClient, calls); return GermplasmGenotype.builder() - .germplasm(germplasm) - .calls(calls.stream().collect(Collectors.groupingBy(BrAPICall::getCallSetDbId))) - .callSets(callSets.stream().collect(Collectors.toMap(BrAPICallSet::getCallSetDbId, callset -> callset))) - .variants(variants.stream().collect(Collectors.toMap(BrAPIVariant::getVariantDbId, variant -> variant))) - .build(); + .germplasm(germplasm) + .calls(calls.stream().collect(Collectors.groupingBy(BrAPICall::getCallSetDbId))) + .callSets(callSets.stream().collect(Collectors.toMap(BrAPICallSet::getCallSetDbId, callset -> callset))) + .variants(variants.stream().collect(Collectors.toMap(BrAPIVariant::getVariantDbId, variant -> variant))) + .build(); } else { return new GermplasmGenotype(); } } + @Override + public List getGenotypeImports(UUID programId) { + log.debug("Fetching genotypeImport data for programId={}", programId); + BiUserTable sampleSubmissionCreatedByUser = BI_USER.as("sampleSubmissionCreatedByUser"); + BiUserTable genotypingImportByUser = BI_USER.as("genotypingImportByUser"); + return dsl.select( + SAMPLE_SUBMISSION.ID, + SAMPLE_SUBMISSION.NAME, + sampleSubmissionCreatedByUser.NAME, + IMPORTER_IMPORT.UPLOAD_FILE_NAME, + IMPORTER_IMPORT.CREATED_AT, + genotypingImportByUser.NAME) + .from(GENOTYPE_IMPORT) + .join(SAMPLE_SUBMISSION).on(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID.eq(SAMPLE_SUBMISSION.ID)) + .join(sampleSubmissionCreatedByUser).on(SAMPLE_SUBMISSION.CREATED_BY.eq(sampleSubmissionCreatedByUser.ID)) + .join(IMPORTER_IMPORT).on(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID.eq(IMPORTER_IMPORT.ID)) + .join(genotypingImportByUser).on(IMPORTER_IMPORT.USER_ID.eq(genotypingImportByUser.ID)) + .where(SAMPLE_SUBMISSION.PROGRAM_ID.eq(programId)) + .and(IMPORTER_IMPORT.PROGRAM_ID.eq(programId)) + .orderBy(IMPORTER_IMPORT.CREATED_AT.desc()) + .fetch(record -> GenotypeImportDetails.builder() + .sampleSubmissionId(record.get(SAMPLE_SUBMISSION.ID)) + .projectNameForSampleSubmission(record.get(SAMPLE_SUBMISSION.NAME)) + .sampleSubmissionCreatedBy(record.get(sampleSubmissionCreatedByUser.NAME)) + .genotypingFileName(record.get(IMPORTER_IMPORT.UPLOAD_FILE_NAME)) + .genotypingImportDate(record.get(IMPORTER_IMPORT.CREATED_AT)) + .genotypingImportBy(record.get(genotypingImportByUser.NAME)) + .build()); + } + + private void createGenotypeImportLink(UUID submissionId, UUID importerImportId, UUID userId) { + OffsetDateTime now = OffsetDateTime.now(); + log.debug("Inserting record into GenotypeImport table for submissionId={}, importerImportId={}, userId={}", submissionId, importerImportId, userId); + dsl.insertInto(GENOTYPE_IMPORT) + .set(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID, submissionId) + .set(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID, importerImportId) + .set(GENOTYPE_IMPORT.CREATED_AT, now) + .set(GENOTYPE_IMPORT.UPDATED_AT, now) + .set(GENOTYPE_IMPORT.CREATED_BY, userId) + .set(GENOTYPE_IMPORT.UPDATED_BY, userId) + .execute(); + } + private boolean validateSamples(Program program, UUID submissionId, byte[] fileContents, ImportUpload upload) throws DoesNotExistException, ApiException { log.debug("Validating samples in submitted VCF file for submission: " + submissionId); Set submissionSampleNames = fetchSubmissionSamples(program, submissionId).stream() - .map(BrAPISample::getSampleName) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + .map(BrAPISample::getSampleName) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); log.debug("searching for the VCF header row"); String[] headerParts = null; @@ -277,15 +329,15 @@ private boolean validateSamples(Program program, UUID submissionId, byte[] fileC boolean foundHeader = false; while (sc.hasNextLine() && !foundHeader) { String line = sc.nextLine(); - if(line.startsWith("#CHROM")) { + if (line.startsWith("#CHROM")) { log.debug("Header row found! -> " + line); foundHeader = true; headerParts = line.split("\t"); } } - if(!foundHeader) { - upload.getProgress().setStatuscode((short)HttpStatus.BAD_REQUEST.getCode()); + if (!foundHeader) { + upload.getProgress().setStatuscode((short) HttpStatus.BAD_REQUEST.getCode()); upload.getProgress().setMessage("Could not find header row in file"); importDAO.updateProgress(upload.getProgress()); return false; @@ -293,22 +345,22 @@ private boolean validateSamples(Program program, UUID submissionId, byte[] fileC List samples = new ArrayList<>(); boolean validHeader = false; - if(headerParts.length >= 8) { + if (headerParts.length >= 8) { validHeader = validateVcfHeader(headerParts); - if(validHeader) { + if (validHeader) { int sampleStart = 8; - if(headerParts[8].equals("FORMAT")) { + if (headerParts[8].equals("FORMAT")) { sampleStart++; } samples.addAll(Arrays.asList(headerParts) - .subList(sampleStart, headerParts.length)); + .subList(sampleStart, headerParts.length)); } } - if(!validHeader) { - upload.getProgress().setStatuscode((short)HttpStatus.BAD_REQUEST.getCode()); + if (!validHeader) { + upload.getProgress().setStatuscode((short) HttpStatus.BAD_REQUEST.getCode()); upload.getProgress().setMessage("Header row is not valid VCF format"); importDAO.updateProgress(upload.getProgress()); return false; @@ -317,13 +369,13 @@ private boolean validateSamples(Program program, UUID submissionId, byte[] fileC log.debug("pulled all the samples from the VCF, now checking each one belongs to the submission"); List samplesMissingSubmission = new ArrayList<>(); samples.forEach(s -> { - if(!submissionSampleNames.contains(s)) { + if (!submissionSampleNames.contains(s)) { samplesMissingSubmission.add(s); } }); - if(!samplesMissingSubmission.isEmpty()) { - upload.getProgress().setStatuscode((short)HttpStatus.BAD_REQUEST.getCode()); + if (!samplesMissingSubmission.isEmpty()) { + upload.getProgress().setStatuscode((short) HttpStatus.BAD_REQUEST.getCode()); upload.getProgress().setMessage("There are samples that are not linked to the selected submission"); importDAO.updateProgress(upload.getProgress()); return false; @@ -334,39 +386,39 @@ private boolean validateSamples(Program program, UUID submissionId, byte[] fileC } private boolean validateVcfHeader(String[] headerParts) { - if(headerParts.length < 8) { + if (headerParts.length < 8) { return false; } - if(!headerParts[0].equals("#CHROM")) { + if (!headerParts[0].equals("#CHROM")) { return false; } - if(!headerParts[1].equals("POS")) { + if (!headerParts[1].equals("POS")) { return false; } - if(!headerParts[2].equals("ID")) { + if (!headerParts[2].equals("ID")) { return false; } - if(!headerParts[3].equals("REF")) { + if (!headerParts[3].equals("REF")) { return false; } - if(!headerParts[4].equals("ALT")) { + if (!headerParts[4].equals("ALT")) { return false; } - if(!headerParts[5].equals("QUAL")) { + if (!headerParts[5].equals("QUAL")) { return false; } - if(!headerParts[6].equals("FILTER")) { + if (!headerParts[6].equals("FILTER")) { return false; } - if(!headerParts[7].equals("INFO")) { + if (!headerParts[7].equals("INFO")) { return false; } @@ -382,7 +434,7 @@ private boolean verifyProgramExists(BrAPIClient genoBrAPIClient, Program program private List fetchSamples(BrAPIClient genoBrAPIClient, Program program, List observationUnits) throws ApiException { log.debug("fetching samples for OUs"); - if(observationUnits.isEmpty()) { + if (observationUnits.isEmpty()) { log.debug("No OUs were supplied, returning"); return Collections.emptyList(); } @@ -406,7 +458,7 @@ private List fetchObservationUnits(BrAPIClient phenoBrAPIC } private List fetchSubmissionSamples(Program program, UUID submissionId) throws ApiException, DoesNotExistException { - if(sampleSubmissionDAO.getBySubmissionId(program, submissionId).isEmpty()) { + if (sampleSubmissionDAO.getBySubmissionId(program, submissionId).isEmpty()) { throw new DoesNotExistException("Could not find sample submission in database"); } @@ -415,7 +467,7 @@ private List fetchSubmissionSamples(Program program, UUID submissio private List fetchCallsets(BrAPIClient genoBrAPIClient, List germplasmSamples) throws ApiException { log.debug("fetching callsets for samples"); - if(germplasmSamples.isEmpty()) { + if (germplasmSamples.isEmpty()) { log.debug("No samples were supplied, returning"); return Collections.emptyList(); } @@ -430,14 +482,14 @@ private List fetchCallsets(BrAPIClient genoBrAPIClient, List fetchCalls(BrAPIClient genoBrAPIClient, List callSets) throws ApiException { log.debug("fetching calls for callsets"); - if(callSets.isEmpty()) { + if (callSets.isEmpty()) { log.debug("No callsets were supplied, returning"); return Collections.emptyList(); } CallsApi callsApi = brAPIEndpointProvider.get(genoBrAPIClient, CallsApi.class); List calls = new ArrayList<>(); - for(BrAPICallSet callSet : callSets) { + for (BrAPICallSet callSet : callSets) { BrAPICallsSearchRequest searchRequest = new BrAPICallsSearchRequest(); searchRequest.setCallSetDbIds(List.of(callSet.getCallSetDbId())); @@ -449,14 +501,14 @@ private List fetchCalls(BrAPIClient genoBrAPIClient, List fetchVariants(BrAPIClient genoBrAPIClient, List calls) throws ApiException { log.debug("fetching variants for calls"); - if(calls.isEmpty()) { + if (calls.isEmpty()) { log.debug("No calls were supplied, returning"); return Collections.emptyList(); } List variantIds = calls.stream() - .map(BrAPICall::getVariantDbId) - .distinct() - .collect(Collectors.toList()); + .map(BrAPICall::getVariantDbId) + .distinct() + .collect(Collectors.toList()); VariantsApi variantsApi = brAPIEndpointProvider.get(genoBrAPIClient, VariantsApi.class); @@ -486,7 +538,7 @@ protected void processSubmission(String gigwaAuthToken, Program program, UUID su OkHttpClient client = new OkHttpClient(); String gigwaProgressToken = submitRequestToGigwa(client, program, submissionId, uploadedFileResult.getLeft(), gigwaAuthToken, progress); - if(checkGigwaProgress(client, gigwaAuthToken, gigwaProgressToken, progress)) { + if (checkGigwaProgress(client, gigwaAuthToken, gigwaProgressToken, progress)) { log.debug("Gigwa import was successful!"); progress.setMessage("Import successful"); progress.setStatuscode((short) HttpStatus.OK.getCode()); @@ -500,9 +552,9 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S Request progressRequest = new Request.Builder() .url(HttpUrl.parse(buildPath("gigwa/progress")) - .newBuilder() - .addQueryParameter("progressToken", progressToken) - .build()) + .newBuilder() + .addQueryParameter("progressToken", progressToken) + .build()) .header(AUTHORIZATION, BEARER + gigwaAuthToken) .build(); @@ -510,7 +562,7 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S while (!completed) { log.debug("checking gigwa progress"); try (Response response = client.newCall(progressRequest) - .execute()) { + .execute()) { if (!response.isSuccessful()) { progress.setStatuscode((short) HttpStatus.INTERNAL_SERVER_ERROR.getCode()); progress.setMessage("An error occurred saving the genotypic data"); @@ -520,8 +572,8 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S AtomicReference error = new AtomicReference<>(); if (response.code() == 200) { String body = Objects.requireNonNull(response.body()) - .string(); - if(body.length() == 0) { + .string(); + if (body.length() == 0) { error.set("No status response returned, assuming error"); } else { log.debug("Progress as of now: " + body); @@ -530,10 +582,10 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S .ifPresent(jsonElement -> error.set(jsonElement.getAsString())); completed = getBooleanValue(gigwaProgress, "complete", false); progress.setMessage(gigwaProgress.get("progressDescription") - .getAsString()); + .getAsString()); importDAO.updateProgress(progress); } - } else if(response.code() == 204) { + } else if (response.code() == 204) { error.set("No status response returned, assuming error"); } @@ -557,6 +609,7 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S /** * Submits the upload request to Gigwa, and returns the progress token + * * @param client * @param program * @param submissionId @@ -569,15 +622,15 @@ private boolean checkGigwaProgress(OkHttpClient client, String gigwaAuthToken, S private String submitRequestToGigwa(OkHttpClient client, Program program, UUID submissionId, String fileUrl, String gigwaAuthToken, ImportProgress progress) throws IOException { Request request = new Request.Builder() .url(HttpUrl.parse(buildPath("gigwa/genotypeImport")) - .newBuilder() - .addQueryParameter("module", program.getKey()) - .addQueryParameter("project", submissionId.toString()) - .addQueryParameter("run", LocalDateTime.now().toString()) - .addQueryParameter("dataFile1", fileUrl) + .newBuilder() + .addQueryParameter("module", program.getKey()) + .addQueryParameter("project", submissionId.toString()) + .addQueryParameter("run", LocalDateTime.now().toString()) + .addQueryParameter("dataFile1", fileUrl) // .addQueryParameter("ploidy", "4") //TODO CHANGE THIS!! it's only for the hackathon!!!! - .build()) + .build()) .header(AUTHORIZATION, BEARER + gigwaAuthToken) .header(X_FORWARDED_FOR, referenceSource) .post(RequestBody.create("", MediaType.parse("text/plain"))) @@ -585,7 +638,7 @@ private String submitRequestToGigwa(OkHttpClient client, Program program, UUID s log.debug("uploading data to Gigwa"); try (Response response = client.newCall(request) - .execute()) { + .execute()) { if (!response.isSuccessful()) { progress.setStatuscode((short) HttpStatus.INTERNAL_SERVER_ERROR.getCode()); progress.setMessage("An error occurred saving the genotypic data"); @@ -600,7 +653,7 @@ private String submitRequestToGigwa(OkHttpClient client, Program program, UUID s private Pair uploadGenotypeData(UUID programId, UUID submissionId, UUID uploadId, byte[] fileContents, String filename) throws IOException, MimeTypeException { log.debug("saving genotype data to S3"); - if(!storageService.listBucketNames().contains(storageService.getDefaultBucketName())) { + if (!storageService.listBucketNames().contains(storageService.getDefaultBucketName())) { log.debug("bucket doesn't exist, creating it"); storageService.createBucket(); } @@ -619,34 +672,34 @@ private String storeMultipartFile(String key, byte[] fileContents, Map parts = new ArrayList<>(); int partNumber = 1; String etag = s3Client.uploadPart(UploadPartRequest.builder() - .bucket(bucketName) - .key(key) - .uploadId(uploadId) - .partNumber(partNumber) - .build(), - software.amazon.awssdk.core.sync.RequestBody.fromBytes(fileContents)) - .eTag(); + .bucket(bucketName) + .key(key) + .uploadId(uploadId) + .partNumber(partNumber) + .build(), + software.amazon.awssdk.core.sync.RequestBody.fromBytes(fileContents)) + .eTag(); parts.add(CompletedPart.builder().partNumber(partNumber).eTag(etag).build()); log.debug("all parts have been uploaded, completing the upload"); CompleteMultipartUploadResponse completeMultipartUploadResponse = s3Client.completeMultipartUpload(CompleteMultipartUploadRequest.builder() - .bucket(bucketName) - .key(key) - .uploadId(uploadId) - .multipartUpload(CompletedMultipartUpload.builder() - .parts(parts) - .build()) - .build()); + .bucket(bucketName) + .key(key) + .uploadId(uploadId) + .multipartUpload(CompletedMultipartUpload.builder() + .parts(parts) + .build()) + .build()); log.debug("upload complete"); return completeMultipartUploadResponse.location(); } @@ -654,7 +707,7 @@ private String storeMultipartFile(String key, byte[] fileContents, Map new DoesNotExistException("Program ID does not exist")); + .stream() + .findFirst() + .orElseThrow(() -> new DoesNotExistException("Program ID does not exist")); } } diff --git a/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java new file mode 100644 index 000000000..13aa79b52 --- /dev/null +++ b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java @@ -0,0 +1,36 @@ +package org.breedinginsight.utilities.response.mappers; + +import lombok.Getter; +import org.breedinginsight.model.GenotypeImportDetails; + +import javax.inject.Singleton; +import java.util.Map; +import java.util.function.Function; + +@Getter +@Singleton +public class GenotypeImportQueryMapper extends AbstractQueryMapper { + + private final Map> fields; + + public GenotypeImportQueryMapper() { + fields = Map.ofEntries( + Map.entry("projectNameForSampleSubmission", GenotypeImportDetails::getProjectNameForSampleSubmission), + Map.entry("sampleSubmissionCreatedBy", GenotypeImportDetails::getSampleSubmissionCreatedBy), + Map.entry("genotypingFileName", GenotypeImportDetails::getGenotypingFileName), + Map.entry("genotypingImportDate", GenotypeImportDetails::getGenotypingImportDate), + Map.entry("genotypingImportBy", GenotypeImportDetails::getGenotypingImportBy) + ); + } + + @Override + public boolean exists(String fieldName) { + return getFields().containsKey(fieldName); + } + + @Override + public Function getField(String fieldName) throws NullPointerException { + if (fields.containsKey(fieldName)) return fields.get(fieldName); + else throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql b/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql new file mode 100644 index 000000000..9d123fd90 --- /dev/null +++ b/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql @@ -0,0 +1,55 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +-- Join table linking sample submissions to genotype imports +create table genotype_import +( + like base_entity including defaults including constraints including indexes, + sample_submission_id uuid not null, + importer_import_id uuid not null, + like base_edit_track_entity including all +); + +alter table genotype_import + add constraint fk_gi_sample_submission + foreign key (sample_submission_id) + references sample_submission (id); + +alter table genotype_import + add constraint fk_gi_importer_import + foreign key (importer_import_id) + references importer_import (id); + +alter table genotype_import + add constraint fk_gi_created_by + foreign key (created_by) + references bi_user (id); + +alter table genotype_import + add constraint fk_gi_updated_by + foreign key (updated_by) + references bi_user (id); + +alter table genotype_import + add constraint uq_gi_importer_import_id + unique (importer_import_id); + +create index idx_gi_sample_submission_id + on genotype_import (sample_submission_id); + +create index idx_gi_importer_import_id + on genotype_import (importer_import_id); \ No newline at end of file From f145612e1eae8e43d4d0b07529df3e3b10407673 Mon Sep 17 00:00:00 2001 From: Keerthi Humsika Kattamudi Date: Sun, 7 Jun 2026 15:35:13 -0500 Subject: [PATCH 3/6] BI-2848: Committing additional code changes and test cases. --- .../v1/request/query/GenotypeImportQuery.java | 52 +++ .../geno/GenotypeDataUploadController.java | 16 +- .../mappers/GenotypeImportQueryMapper.java | 1 + ...peDataUploadControllerIntegrationTest.java | 232 ++++++++++++- ...gwaGenotypeServiceImplIntegrationTest.java | 319 ++++++++++++------ 5 files changed, 507 insertions(+), 113 deletions(-) create mode 100644 src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java diff --git a/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java new file mode 100644 index 000000000..5e45fcddd --- /dev/null +++ b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java @@ -0,0 +1,52 @@ +package org.breedinginsight.api.model.v1.request.query; + +import io.micronaut.core.annotation.Introspected; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Introspected +public class GenotypeImportQuery extends QueryParams { + + private String sampleSubmissionId; + private String projectNameForSampleSubmission; + private String sampleSubmissionCreatedBy; + private String genotypingFileName; + private String genotypingImportDate; + private String genotypingImportBy; + + public SearchRequest constructSearchRequest() { + List filters = new ArrayList<>(); + + if (!StringUtils.isBlank(getSampleSubmissionId())) { + filters.add(constructFilterRequest("sampleSubmissionId", getSampleSubmissionId())); + } + if (!StringUtils.isBlank(getProjectNameForSampleSubmission())) { + filters.add(constructFilterRequest("projectNameForSampleSubmission", getProjectNameForSampleSubmission())); + } + if (!StringUtils.isBlank(getSampleSubmissionCreatedBy())) { + filters.add(constructFilterRequest("sampleSubmissionCreatedBy", getSampleSubmissionCreatedBy())); + } + if (!StringUtils.isBlank(getGenotypingFileName())) { + filters.add(constructFilterRequest("genotypingFileName", getGenotypingFileName())); + } + if (!StringUtils.isBlank(getGenotypingImportDate())) { + filters.add(constructFilterRequest("genotypingImportDate", getGenotypingImportDate())); + } + if (!StringUtils.isBlank(getGenotypingImportBy())) { + filters.add(constructFilterRequest("genotypingImportBy", getGenotypingImportBy())); + } + + return new SearchRequest(filters); + } + + private FilterRequest constructFilterRequest(String field, String value) { + return FilterRequest.builder() + .field(field) + .value(value) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java index 9b698dbcd..dd439103f 100644 --- a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java @@ -7,7 +7,8 @@ import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; import org.breedinginsight.api.auth.*; -import org.breedinginsight.api.model.v1.request.query.QueryParams; +import org.breedinginsight.api.model.v1.request.query.GenotypeImportQuery; +import org.breedinginsight.api.model.v1.request.query.SearchRequest; import org.breedinginsight.api.model.v1.response.DataResponse; import org.breedinginsight.api.model.v1.response.Response; import org.breedinginsight.api.model.v1.validators.QueryValid; @@ -44,19 +45,26 @@ public GenotypeDataUploadController(GenotypeService genoService, SecurityService this.genotypeImportQueryMapper = genotypeImportQueryMapper; } - @Get("programs/{programId}/geno/imports{?queryParams*}") + @Get("programs/{programId}/geno/imports{?genotypeImportQuery*}") @Produces(MediaType.APPLICATION_JSON) @ProgramSecured(roleGroups = ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES) public HttpResponse>> getGenotypeImports( @PathVariable UUID programId, - @QueryValue @QueryValid(using = GenotypeImportQueryMapper.class) @Valid QueryParams queryParams) { + @QueryValue @QueryValid(using = GenotypeImportQueryMapper.class) @Valid GenotypeImportQuery genotypeImportQuery) { Optional program = programService.getById(programId); if (program.isEmpty()) { log.info("programId not found: {}", programId.toString()); return HttpResponse.notFound(); } - return ResponseUtils.getQueryResponse(genoService.getGenotypeImports(programId), genotypeImportQueryMapper, queryParams); + SearchRequest searchRequest = genotypeImportQuery.constructSearchRequest(); + + return ResponseUtils.getQueryResponse( + genoService.getGenotypeImports(programId), + genotypeImportQueryMapper, + searchRequest, + genotypeImportQuery + ); } @Post("programs/{programId}/submissions/{submissionId}/geno/import") diff --git a/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java index 13aa79b52..bc1e291aa 100644 --- a/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java +++ b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java @@ -15,6 +15,7 @@ public class GenotypeImportQueryMapper extends AbstractQueryMapper response = client.exchange( POST(String.format("/programs/%s/submissions/%s/geno/import", program.getId(), submissionId), multipartBody()) @@ -137,9 +161,205 @@ void experimentScopedUploadRouteIsRemoved() { verifyNoInteractions(genotypeService); } + @Test + void getGenotypeImportsReturnsPagedAndSortedResponse() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("22222222-2222-2222-2222-222222222222")) + .projectNameForSampleSubmission("Newer Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("newer.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("Importer B") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(older, newer))) // mutable list required because ResponseUtils sorts in place + .when(genotypeService) + .getGenotypeImports(program.getId()); + + HttpResponse response = client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=1&sortField=genotypingImportDate&sortOrder=DESC", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(2, pagination.get("totalCount").getAsInt()); + assertEquals(1, pagination.get("pageSize").getAsInt()); + assertEquals(2, pagination.get("totalPages").getAsInt()); + assertEquals(1, pagination.get("currentPage").getAsInt()); + assertEquals(1, data.size()); + + JsonObject firstRow = data.get(0).getAsJsonObject(); + assertEquals("22222222-2222-2222-2222-222222222222", firstRow.get("sampleSubmissionId").getAsString()); + assertEquals("Newer Submission", firstRow.get("projectNameForSampleSubmission").getAsString()); + assertEquals("Test User", firstRow.get("sampleSubmissionCreatedBy").getAsString()); + assertEquals("newer.vcf", firstRow.get("genotypingFileName").getAsString()); + assertEquals("Importer B", firstRow.get("genotypingImportBy").getAsString()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verify(genotypeService).getGenotypeImports(program.getId()); + } + + @Test + void getGenotypeImportsReturnsNotFoundWhenProgramLookupFails() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.empty()).when(programService).getById(program.getId()); + + HttpClientResponseException exception = assertThrows(HttpClientResponseException.class, () -> client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=10", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst()); + + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verifyNoInteractions(genotypeService); + } + + @Test + void getGenotypeImportsRejectsInvalidSortField() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + + HttpClientResponseException exception = assertThrows(HttpClientResponseException.class, () -> client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=10&sortField=badField&sortOrder=DESC", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst()); + + assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); + + verify(programService).getBrapiEndpoints(program.getId()); + verifyNoInteractions(genotypeService); + } + + @Test + void getGenotypeImportsFiltersByProjectNameForSampleSubmission() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("22222222-2222-2222-2222-222222222222")) + .projectNameForSampleSubmission("Newer Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("newer.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("Importer B") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(older, newer))) //safe mutable list + .when(genotypeService) + .getGenotypeImports(program.getId()); + String param = URLEncoder.encode("Newer Submission", StandardCharsets.UTF_8); + HttpResponse response = client.exchange( + GET(String.format( + "/programs/%s/geno/imports?page=1&pageSize=10&projectNameForSampleSubmission=" + param, + program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(1, pagination.get("totalCount").getAsInt()); + assertEquals(10, pagination.get("pageSize").getAsInt()); + assertEquals(1, pagination.get("totalPages").getAsInt()); + assertEquals(1, pagination.get("currentPage").getAsInt()); + assertEquals(1, data.size()); + + JsonObject firstRow = data.get(0).getAsJsonObject(); + assertEquals("22222222-2222-2222-2222-222222222222", firstRow.get("sampleSubmissionId").getAsString()); + assertEquals("Newer Submission", firstRow.get("projectNameForSampleSubmission").getAsString()); + assertEquals("newer.vcf", firstRow.get("genotypingFileName").getAsString()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verify(genotypeService).getGenotypeImports(program.getId()); + } + + @Test + void getGenotypeImportsReturnsEmptyDataWhenFiltersDoNotMatch() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails row = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(row))) //safe mutable list + .when(genotypeService) + .getGenotypeImports(program.getId()); + + HttpResponse response = client.exchange( + GET(String.format( + "/programs/%s/geno/imports?page=1&pageSize=10&projectNameForSampleSubmission=DoesNotMatch", + program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(0, pagination.get("totalCount").getAsInt()); + assertEquals(0, data.size()); + + verify(programService, times(1)).getBrapiEndpoints(program.getId()); + verify(programService, times(1)).getById(program.getId()); + verify(genotypeService, times(1)).getGenotypeImports(program.getId()); + } + private MultipartBody multipartBody() { return MultipartBody.builder() - .addPart("file", new File("src/test/resources/files/geno/sample.vcf")) - .build(); + .addPart("file", new File("src/test/resources/files/geno/sample.vcf")) + .build(); + } + + private ProgramBrAPIEndpoints getBrAPIEndpoints() { + return ProgramBrAPIEndpoints.builder() + .coreUrl(Optional.of("http://localhost:8081/")) + .phenoUrl(Optional.of("http://localhost:8081/")) + .genoUrl(Optional.of("http://localhost:8081/")) + .build(); } } diff --git a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java index c1009977b..65066dac7 100644 --- a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java +++ b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java @@ -38,10 +38,10 @@ import org.brapi.v2.model.geno.response.BrAPISampleListResponseResult; import org.brapi.v2.model.germ.BrAPIGermplasm; import org.breedinginsight.DatabaseTest; +import org.breedinginsight.brapi.v2.dao.impl.ImportMappingDAOImpl; import org.breedinginsight.brapps.importer.daos.BrAPISampleDAO; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.daos.ImportMappingDAO; -import org.breedinginsight.brapi.v2.dao.impl.ImportMappingDAOImpl; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; @@ -52,11 +52,7 @@ import org.breedinginsight.daos.UserDAO; import org.breedinginsight.daos.impl.ProgramDAOImpl; import org.breedinginsight.daos.impl.UserDAOImpl; -import org.breedinginsight.model.BrAPIConstants; -import org.breedinginsight.model.GermplasmGenotype; -import org.breedinginsight.model.Program; -import org.breedinginsight.model.SampleSubmission; -import org.breedinginsight.model.User; +import org.breedinginsight.model.*; import org.breedinginsight.services.brapi.BrAPIClientProvider; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; import org.breedinginsight.services.brapi.BrAPIProvider; @@ -243,12 +239,12 @@ public GigwaGenotypeServiceImplIntegrationTest() { .withEnv("GIGWA.serversAllowedToImport", gigwaAllowedServer) .waitingFor( Wait.forHttp("/gigwa") - .forStatusCode(200) - .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); + .forStatusCode(200) + .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); gigwa.start(); localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack") - .withTag("3.0.2")) + .withTag("3.0.2")) .withServices(LocalStackContainer.Service.S3) .withNetwork(super.getNetwork()) .withNetworkAliases("localstack") @@ -261,7 +257,7 @@ public GigwaGenotypeServiceImplIntegrationTest() { public Map getProperties() { Map properties = super.getProperties(); - properties.put("gigwa.host", "http://"+gigwa.getContainerIpAddress()+":"+gigwa.getMappedPort(8080)+"/"); + properties.put("gigwa.host", "http://" + gigwa.getContainerIpAddress() + ":" + gigwa.getMappedPort(8080) + "/"); properties.put("gigwa.username", "gigwadmin"); properties.put("gigwa.password", "nimda"); @@ -289,7 +285,7 @@ public void setup() throws IllegalAccessException, NoSuchFieldException { storageService = applicationContext.getBean(SimpleStorageService.class, Qualifiers.byName("genotype")); storageService.createBucket(); - } + } @AfterAll public void teardown() { @@ -310,33 +306,33 @@ public void testUpload() throws ApiException, AuthorizationException { BrAPIClient brAPIClient = new BrAPIClient(gigwaHost + "gigwa/rest/brapi/v2"); Authentication authorizationToken = brAPIClient.getAuthentication("AuthorizationToken"); - if(authorizationToken instanceof OAuth) { - ((OAuth)authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); + if (authorizationToken instanceof OAuth) { + ((OAuth) authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); } ProgramsApi programsApi = new ProgramsApi(brAPIClient); try { ApiResponse brAPIProgramListResponseApiResponse = programsApi.programsGet(ProgramQueryParams.builder() - .programDbId(programKey) - .build()); + .programDbId(programKey) + .build()); assertEquals(1, - brAPIProgramListResponseApiResponse.getBody() - .getResult() - .getData() - .size()); + brAPIProgramListResponseApiResponse.getBody() + .getResult() + .getData() + .size()); StudiesApi studiesApi = new StudiesApi(brAPIClient); ApiResponse brAPIStudyListResponseApiResponse = studiesApi.studiesGet(StudyQueryParams.builder() - .build()); + .build()); assertEquals(1, - brAPIStudyListResponseApiResponse.getBody() - .getResult() - .getData() - .stream() - .filter(brAPIStudy -> brAPIStudy.getStudyName() - .equals(submissionId.toString())) - .count()); + brAPIStudyListResponseApiResponse.getBody() + .getResult() + .getData() + .stream() + .filter(brAPIStudy -> brAPIStudy.getStudyName() + .equals(submissionId.toString())) + .count()); } catch (ApiException e) { System.err.println(e.getMessage()); System.err.println(e.getResponseBody()); @@ -358,11 +354,11 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep SamplesApi mockSamplesApi = spy(new SamplesApi()); BrAPISample sample = new BrAPISample().sampleName("USDAMSP1_A01") - .germplasmDbId(germplasm.getGermplasmDbId()); + .germplasmDbId(germplasm.getGermplasmDbId()); doReturn(new ApiResponse<>(200, - new HashMap<>(), - Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), - Optional.empty()))) + new HashMap<>(), + Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), + Optional.empty()))) .when(mockSamplesApi).searchSamplesPost(any(BrAPISampleSearchRequest.class)); doReturn(mockSamplesApi).when(brAPIEndpointProvider).get(any(BrAPIClient.class), eq(SamplesApi.class)); @@ -374,8 +370,8 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep verify(brAPIEndpointProvider, never()).get(any(BrAPIClient.class), eq(ObservationUnitsApi.class)); verify(mockSamplesApi).searchSamplesPost(argThat(searchRequest -> searchRequest.getGermplasmDbIds() != null && - searchRequest.getGermplasmDbIds().contains(germplasm.getGermplasmDbId()) && - (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); + searchRequest.getGermplasmDbIds().contains(germplasm.getGermplasmDbId()) && + (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); assertNotNull(germplasmGenotype); assertFalse(germplasmGenotype.getCalls().isEmpty()); assertFalse(germplasmGenotype.getCallSets().isEmpty()); @@ -388,31 +384,87 @@ public void testSubmitValidFile() throws IOException, ApiException { String programKey = "TESTSUBMITVALID"; UUID submissionId = UUID.randomUUID(); - Scanner sc = new Scanner(new FileInputStream("src/test/resources/files/geno/sample.vcf"), "UTF-8"); - String[] headerParts = null; - boolean foundHeader = false; - while (sc.hasNextLine() && !foundHeader) { - String line = sc.nextLine(); - if(line.startsWith("#CHROM")) { - foundHeader = true; - headerParts = line.split("\t"); - } - } - assertTrue(foundHeader, "Could not find sample.vcf header file"); - - List samples = new ArrayList<>(); - for(int i = 9; i < headerParts.length; i++) { - samples.add(new BrAPISample().sampleName(headerParts[i])); - } + List samples = buildSamplesFromValidVcf(); setupMocksForSubmitGenoData(programId, submissionId, samples); AtomicReference importResponse = new AtomicReference<>(); - assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> importResponse.set(submitGenoData(programId, programKey, submissionId, "sample.vcf")), "Upload did not complete within the time period"); + assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> + importResponse.set(submitGenoData(programId, programKey, submissionId, "sample.vcf")), + "Upload did not complete within the time period"); ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.ACCEPTED.getCode(), response.getProgress().getStatuscode(), "Error importing geno file: " + response.getProgress().getMessage()); + assertEquals((short) HttpStatus.ACCEPTED.getCode(), + response.getProgress().getStatuscode(), + "Error importing geno file: " + response.getProgress().getMessage()); + + Integer joinRowCount = dsl.fetchOne( + "select count(*) from genotype_import where sample_submission_id = ?::uuid", + submissionId + ).into(Integer.class); + assertEquals(1, joinRowCount); + } + + @Test + public void testGetGenotypeImportsReturnsRowsIncludingSampleSubmissionId() throws Exception { + UUID programId = UUID.randomUUID(); + String programKey = "TESTGETGENOIMPORTS"; + UUID olderSubmissionId = UUID.randomUUID(); + UUID newerSubmissionId = UUID.randomUUID(); + + List samples = buildSamplesFromValidVcf(); + + setupMocksForSubmitGenoData(programId, olderSubmissionId, samples); + AtomicReference olderImportResponse = new AtomicReference<>(); + assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> + olderImportResponse.set(submitGenoData(programId, programKey, olderSubmissionId, "sample.vcf")), + "First submit did not complete within the time period"); + + assertNotNull(olderImportResponse.get()); + assertNotNull(olderImportResponse.get().getProgress()); + assertEquals((short) HttpStatus.ACCEPTED.getCode(), olderImportResponse.get().getProgress().getStatuscode()); + + Thread.sleep(10L); // keeps created_at ordering deterministic + + setupMocksForSubmitGenoData(programId, newerSubmissionId, samples); + AtomicReference newerImportResponse = new AtomicReference<>(); + assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> + newerImportResponse.set(submitGenoData(programId, programKey, newerSubmissionId, "sample.vcf")), + "Second submit did not complete within the time period"); + + assertNotNull(newerImportResponse.get()); + assertNotNull(newerImportResponse.get().getProgress()); + assertEquals((short) HttpStatus.ACCEPTED.getCode(), newerImportResponse.get().getProgress().getStatuscode()); + + List rows = gigwaGenoStorageService.getGenotypeImports(programId); + + assertNotNull(rows); + assertEquals(2, rows.size()); + + assertEquals(newerSubmissionId, rows.get(0).getSampleSubmissionId()); + assertEquals("Submission " + newerSubmissionId, rows.get(0).getProjectNameForSampleSubmission()); + assertEquals("sample.vcf", rows.get(0).getGenotypingFileName()); + assertNotNull(rows.get(0).getGenotypingImportDate()); + assertEquals("system", rows.get(0).getSampleSubmissionCreatedBy()); + assertEquals("system", rows.get(0).getGenotypingImportBy()); + + assertEquals(olderSubmissionId, rows.get(1).getSampleSubmissionId()); + assertEquals("Submission " + olderSubmissionId, rows.get(1).getProjectNameForSampleSubmission()); + assertEquals("sample.vcf", rows.get(1).getGenotypingFileName()); + assertNotNull(rows.get(1).getGenotypingImportDate()); + assertEquals("system", rows.get(1).getSampleSubmissionCreatedBy()); + assertEquals("system", rows.get(1).getGenotypingImportBy()); + } + + @Test + public void testGetGenotypeImportsReturnsEmptyListWhenNoImportsExist() { + UUID programId = UUID.randomUUID(); + + List rows = gigwaGenoStorageService.getGenotypeImports(programId); + + assertNotNull(rows); + assertTrue(rows.isEmpty()); } @Test @@ -427,7 +479,7 @@ public void testSubmitInvalidHeader() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("Header row is not valid VCF format", response.getProgress().getMessage()); } @@ -445,7 +497,7 @@ public void testSubmitMissingSubmissionSamples() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("There are samples that are not linked to the selected submission", response.getProgress().getMessage()); } @@ -455,96 +507,157 @@ private void setupMocksForSubmitGenoData(UUID programId, UUID submissionId, List submission.setProgramId(programId); doReturn(List.of(submission)).when(sampleSubmissionDAO) - .getBySubmissionId(any(Program.class), eq(submissionId)); + .getBySubmissionId(any(Program.class), eq(submissionId)); doReturn(samples).when(sampleDAO) - .readSamplesBySubmissionIds(any(Program.class), eq(List.of(submissionId.toString()))); + .readSamplesBySubmissionIds(any(Program.class), eq(List.of(submissionId.toString()))); } private void uploadGenoData(UUID programId, String programKey, UUID submissionId, UUID importId) throws AuthorizationException, MimeTypeException, IOException, ApiException { Program program = Program.builder() - .id(programId) - .key(programKey) - .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) - .build(); + .id(programId) + .key(programKey) + .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) + .build(); doReturn(List.of(program)).when(programDAO) - .get(any(UUID.class)); + .get(any(UUID.class)); User user = User.builder() - .id(UUID.randomUUID()) - .build(); + .id(UUID.randomUUID()) + .build(); doReturn(Optional.of(user)).when(userDAO) - .getUser(any(UUID.class)); + .getUser(any(UUID.class)); ImportMapping mapping = ImportMapping.builder() - .build(); + .build(); doReturn(List.of(mapping)).when(importMappingDAO) - .getSystemMappingByName(any(String.class)); + .getSystemMappingByName(any(String.class)); doAnswer(invocation -> { var importEntity = invocation.getArgument(0, ImporterImportEntity.class); importEntity.setId(importId); return importEntity; }).when(importDAO) - .insert(any(ImporterImportEntity.class)); + .insert(any(ImporterImportEntity.class)); ImportProgress progress = ImportProgress.builder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedAt(OffsetDateTime.now()) - .updatedBy(user.getId()) - .statuscode((short) HttpStatus.ACCEPTED.getCode()) - .message("Uploading file") - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedAt(OffsetDateTime.now()) + .updatedBy(user.getId()) + .statuscode((short) HttpStatus.ACCEPTED.getCode()) + .message("Uploading file") + .build(); ImportUpload importUpload = ImportUpload.uploadBuilder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedBy(user.getId()) - .updatedAt(OffsetDateTime.now()) - .programId(program.getId()) - .importerProgressId(progress.getId()) - .importerMappingId(mapping.getId()) - .id(importId) - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedBy(user.getId()) + .updatedAt(OffsetDateTime.now()) + .programId(program.getId()) + .importerProgressId(progress.getId()) + .importerMappingId(mapping.getId()) + .id(importId) + .build(); System.out.println("====================== program ID: " + program.getId() + " ==============="); System.out.println("=================== submission ID: " + submissionId + " ==============="); gigwaGenoStorageService.processSubmission(gigwaGenoStorageService.getAuthToken(), program, submissionId, new TestFileUpload("src/test/resources/files/geno/sample.vcf", MediaType.of("application/vcard")).getBytes(), "sample.vcf", importUpload, progress); } - private ImportResponse submitGenoData(UUID programId, String programKey, UUID submissionId, String file) throws AuthorizationException, IOException, ApiException, DoesNotExistException { + private ImportResponse submitGenoData(UUID programId, String programKey, UUID submissionId, String file) + throws AuthorizationException, IOException, ApiException, DoesNotExistException { + + UUID userId = dsl.fetchOne("select id from bi_user where name = 'system' limit 1").into(UUID.class); + UUID speciesId = dsl.fetchOne("select id from species limit 1").into(UUID.class); + UUID mappingId = dsl.fetchOne("select id from importer_mapping where name = 'GenotypicDataImport' limit 1").into(UUID.class); + + assertNotNull(userId); + assertNotNull(speciesId); + assertNotNull(mappingId); + + dsl.execute( + "insert into program (id, species_id, name, abbreviation, documentation_url, objective, key, created_by, updated_by) " + + "values (?::uuid, ?::uuid, ?, ?, ?, ?, ?, ?::uuid, ?::uuid) " + + "on conflict (id) do nothing", + programId, speciesId, "Submit Geno Program", programKey, "localhost:8080", "test", programKey, userId, userId); + + dsl.execute( + "insert into sample_submission (id, name, program_id, created_by, updated_by) " + + "values (?::uuid, ?, ?::uuid, ?::uuid, ?::uuid) " + + "on conflict (id) do nothing", + submissionId, "Submission " + submissionId, programId, userId, userId); + Program program = Program.builder() - .id(programId) - .key(programKey) - .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) - .build(); - doReturn(List.of(program)).when(programDAO) - .get(any(UUID.class)); + .id(programId) + .key(programKey) + .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) + .build(); + doReturn(List.of(program)).when(programDAO).get(any(UUID.class)); User user = User.builder() - .id(UUID.randomUUID()) - .build(); - doReturn(Optional.of(user)).when(userDAO) - .getUser(any(UUID.class)); + .id(userId) + .build(); + doReturn(Optional.of(user)).when(userDAO).getUser(any(UUID.class)); ImportMapping mapping = ImportMapping.builder() - .build(); - doReturn(List.of(mapping)).when(importMappingDAO) - .getSystemMappingByName(any(String.class)); + .id(mappingId) + .build(); + doReturn(List.of(mapping)).when(importMappingDAO).getSystemMappingByName(any(String.class)); UUID importId = UUID.randomUUID(); doAnswer(invocation -> { - var importEntity = invocation.getArgument(0, ImporterImportEntity.class); + ImporterImportEntity importEntity = invocation.getArgument(0, ImporterImportEntity.class); importEntity.setId(importId); - return importEntity; - }).when(importDAO) - .insert(any(ImporterImportEntity.class)); + + dsl.execute( + "insert into importer_import " + + "(id, program_id, user_id, importer_mapping_id, upload_file_name, created_at, updated_at, created_by, updated_by) " + + "values (?::uuid, ?::uuid, ?::uuid, ?::uuid, ?, ?::timestamptz, ?::timestamptz, ?::uuid, ?::uuid)", + importId, + importEntity.getProgramId(), + userId, + mappingId, + importEntity.getUploadFileName(), + importEntity.getCreatedAt(), + importEntity.getUpdatedAt(), + userId, + userId + ); + + return null; + }).when(importDAO).insert(any(ImporterImportEntity.class)); doReturn(new BrAPIClient("", 300000)).when(programDAO).getCoreClient(any(UUID.class)); doReturn(new BrAPIClient("", 300000)).when(programDAO).getPhenoClient(any(UUID.class)); - System.out.println("====================== program ID: " + program.getId() + " ==============="); - System.out.println("=================== submission ID: " + submissionId + " ==============="); - return gigwaGenoStorageService.submitGenotypeData(user.getId(), programId, submissionId, new TestFileUpload("src/test/resources/files/geno/"+file, MediaType.of("application/vcard"))); + return gigwaGenoStorageService.submitGenotypeData( + user.getId(), + programId, + submissionId, + new TestFileUpload("src/test/resources/files/geno/" + file, MediaType.of("application/vcard")) + ); + } + + private List buildSamplesFromValidVcf() throws IOException { + try (Scanner sc = new Scanner(new FileInputStream("src/test/resources/files/geno/sample.vcf"), "UTF-8")) { + String[] headerParts = null; + boolean foundHeader = false; + while (sc.hasNextLine() && !foundHeader) { + String line = sc.nextLine(); + if (line.startsWith("#CHROM")) { + foundHeader = true; + headerParts = line.split("\t"); + } + } + + assertTrue(foundHeader, "Could not find sample.vcf header file"); + + List samples = new ArrayList<>(); + for (int i = 9; i < headerParts.length; i++) { + samples.add(new BrAPISample().sampleName(headerParts[i])); + } + + return samples; + } } private class TestFileUpload implements CompletedFileUpload { From a2fbc402ef8cd4e275f1b83b11b3b3896dc0193c Mon Sep 17 00:00:00 2001 From: Keerthi Humsika Kattamudi Date: Mon, 8 Jun 2026 11:40:09 -0500 Subject: [PATCH 4/6] BI-2848: Committing latest code changes. --- .../services/geno/impl/GigwaGenotypeServiceImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java index b2b5f05f3..55b184ec0 100644 --- a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java +++ b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java @@ -42,6 +42,7 @@ import org.breedinginsight.brapps.importer.daos.ImportMappingDAO; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; +import static org.breedinginsight.dao.db.Tables.IMPORTER_PROGRESS; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; import org.breedinginsight.dao.db.tables.BiUserTable; @@ -190,8 +191,6 @@ public ImportResponse submitGenotypeData(UUID userId, UUID programId, UUID submi .build(); importDAO.insert(importUpload); - //logic to add record to the new JOIN table - createGenotypeImportLink(submissionId, importUpload.getId(), user.getId()); return importUpload; }); @@ -288,9 +287,11 @@ public List getGenotypeImports(UUID programId) { .join(SAMPLE_SUBMISSION).on(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID.eq(SAMPLE_SUBMISSION.ID)) .join(sampleSubmissionCreatedByUser).on(SAMPLE_SUBMISSION.CREATED_BY.eq(sampleSubmissionCreatedByUser.ID)) .join(IMPORTER_IMPORT).on(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID.eq(IMPORTER_IMPORT.ID)) + .join(IMPORTER_PROGRESS).on(IMPORTER_IMPORT.IMPORTER_PROGRESS_ID.eq(IMPORTER_PROGRESS.ID)) .join(genotypingImportByUser).on(IMPORTER_IMPORT.USER_ID.eq(genotypingImportByUser.ID)) .where(SAMPLE_SUBMISSION.PROGRAM_ID.eq(programId)) .and(IMPORTER_IMPORT.PROGRAM_ID.eq(programId)) + .and(IMPORTER_PROGRESS.STATUSCODE.eq((short) HttpStatus.OK.getCode())) .orderBy(IMPORTER_IMPORT.CREATED_AT.desc()) .fetch(record -> GenotypeImportDetails.builder() .sampleSubmissionId(record.get(SAMPLE_SUBMISSION.ID)) @@ -540,6 +541,8 @@ protected void processSubmission(String gigwaAuthToken, Program program, UUID su if (checkGigwaProgress(client, gigwaAuthToken, gigwaProgressToken, progress)) { log.debug("Gigwa import was successful!"); + //logic to add record to the new JOIN table + createGenotypeImportLink(submissionId, upload.getId(), upload.getCreatedBy()); progress.setMessage("Import successful"); progress.setStatuscode((short) HttpStatus.OK.getCode()); importDAO.updateProgress(progress); From e0d3d37fe743ee06fe7d6154e1d87321dda2465e Mon Sep 17 00:00:00 2001 From: Keerthi Humsika Kattamudi Date: Sun, 14 Jun 2026 16:58:52 -0500 Subject: [PATCH 5/6] BI-2848: Addressing PR comments. --- .../v1/request/query/GenotypeImportQuery.java | 17 ++ .../geno/GenotypeDataUploadController.java | 16 ++ .../model/GenotypeImportDetails.java | 35 ++++ .../services/geno/GenotypeService.java | 16 ++ .../geno/impl/GigwaGenotypeServiceImpl.java | 57 +----- .../mappers/GenotypeImportQueryMapper.java | 17 ++ ...gwaGenotypeServiceImplIntegrationTest.java | 185 ++++++++---------- 7 files changed, 190 insertions(+), 153 deletions(-) diff --git a/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java index 5e45fcddd..a9d596bcf 100644 --- a/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java +++ b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java @@ -1,3 +1,20 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.breedinginsight.api.model.v1.request.query; import io.micronaut.core.annotation.Introspected; diff --git a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java index dd439103f..9d07bf79c 100644 --- a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java @@ -1,3 +1,19 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.breedinginsight.api.v1.controller.geno; import io.micronaut.http.HttpResponse; diff --git a/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java index 8f3282cae..9082750ed 100644 --- a/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java +++ b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java @@ -1,3 +1,20 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.breedinginsight.model; import io.micronaut.core.annotation.Introspected; @@ -8,10 +25,15 @@ import lombok.experimental.Accessors; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; +import org.breedinginsight.dao.db.tables.BiUserTable; +import org.jooq.Record; import java.time.OffsetDateTime; import java.util.UUID; +import static org.breedinginsight.dao.db.Tables.IMPORTER_IMPORT; +import static org.breedinginsight.dao.db.Tables.SAMPLE_SUBMISSION; + @Getter @Setter @Accessors(chain = true) @@ -27,4 +49,17 @@ public class GenotypeImportDetails { private String genotypingFileName; private OffsetDateTime genotypingImportDate; private String genotypingImportBy; + + public static GenotypeImportDetails parseSqlRecord(Record record, + BiUserTable sampleSubmissionCreatedByUser, + BiUserTable genotypingImportByUser) { + return GenotypeImportDetails.builder() + .sampleSubmissionId(record.get(SAMPLE_SUBMISSION.ID)) + .projectNameForSampleSubmission(record.get(SAMPLE_SUBMISSION.NAME)) + .sampleSubmissionCreatedBy(record.get(sampleSubmissionCreatedByUser.NAME)) + .genotypingFileName(record.get(IMPORTER_IMPORT.UPLOAD_FILE_NAME)) + .genotypingImportDate(record.get(IMPORTER_IMPORT.CREATED_AT)) + .genotypingImportBy(record.get(genotypingImportByUser.NAME)) + .build(); + } } diff --git a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java index e7b2d6847..3ed088137 100644 --- a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java +++ b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java @@ -1,3 +1,19 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.breedinginsight.services.geno; import io.micronaut.http.multipart.CompletedFileUpload; diff --git a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java index 2d0d7bf8a..562ec57bc 100644 --- a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java +++ b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java @@ -40,13 +40,13 @@ import org.breedinginsight.brapps.importer.daos.ImportMappingDAO; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; -import static org.breedinginsight.dao.db.Tables.IMPORTER_PROGRESS; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; -import org.breedinginsight.dao.db.tables.BiUserTable; +import org.breedinginsight.daos.GenotypeImportDAO; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.SampleSubmissionDAO; import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.GenotypeImportDetails; import org.breedinginsight.model.GermplasmGenotype; import org.breedinginsight.model.Program; import org.breedinginsight.model.User; @@ -74,12 +74,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import org.breedinginsight.model.GenotypeImportDetails; -import static org.breedinginsight.dao.db.Tables.BI_USER; -import static org.breedinginsight.dao.db.Tables.GENOTYPE_IMPORT; -import static org.breedinginsight.dao.db.Tables.IMPORTER_IMPORT; -import static org.breedinginsight.dao.db.Tables.SAMPLE_SUBMISSION; - @Singleton @Slf4j public class GigwaGenotypeServiceImpl implements GenotypeService { @@ -104,7 +98,7 @@ public class GigwaGenotypeServiceImpl implements GenotypeService { private final ImportDAO importDAO; private final SampleSubmissionDAO sampleSubmissionDAO; private final BrAPISampleDAO sampleDAO; - + private final GenotypeImportDAO genotypeImportDAO; private final ImportMappingDAO importMappingDAO; private final SimpleStorageService storageService; @@ -130,6 +124,7 @@ public GigwaGenotypeServiceImpl(@Property(name = "gigwa.host") String gigwaHost, ImportDAO importDAO, SampleSubmissionDAO sampleSubmissionDAO, BrAPISampleDAO sampleDAO, + GenotypeImportDAO genotypeImportDAO, ImportMappingDAO importMappingDAO, @Named("genotype") SimpleStorageService storageService, S3Client s3Client, @@ -142,6 +137,7 @@ public GigwaGenotypeServiceImpl(@Property(name = "gigwa.host") String gigwaHost, this.username = username; this.password = password; this.referenceSource = referenceSource; + this.genotypeImportDAO = genotypeImportDAO; this.gson = new GsonBuilder().create(); this.programDAO = programDAO; this.userDAO = userDAO; @@ -276,47 +272,8 @@ public GermplasmGenotype retrieveGenotypeData(UUID programId, UUID germplasmId) @Override public List getGenotypeImports(UUID programId) { - log.debug("Fetching genotypeImport data for programId={}", programId); - BiUserTable sampleSubmissionCreatedByUser = BI_USER.as("sampleSubmissionCreatedByUser"); - BiUserTable genotypingImportByUser = BI_USER.as("genotypingImportByUser"); - return dsl.select( - SAMPLE_SUBMISSION.ID, - SAMPLE_SUBMISSION.NAME, - sampleSubmissionCreatedByUser.NAME, - IMPORTER_IMPORT.UPLOAD_FILE_NAME, - IMPORTER_IMPORT.CREATED_AT, - genotypingImportByUser.NAME) - .from(GENOTYPE_IMPORT) - .join(SAMPLE_SUBMISSION).on(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID.eq(SAMPLE_SUBMISSION.ID)) - .join(sampleSubmissionCreatedByUser).on(SAMPLE_SUBMISSION.CREATED_BY.eq(sampleSubmissionCreatedByUser.ID)) - .join(IMPORTER_IMPORT).on(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID.eq(IMPORTER_IMPORT.ID)) - .join(IMPORTER_PROGRESS).on(IMPORTER_IMPORT.IMPORTER_PROGRESS_ID.eq(IMPORTER_PROGRESS.ID)) - .join(genotypingImportByUser).on(IMPORTER_IMPORT.USER_ID.eq(genotypingImportByUser.ID)) - .where(SAMPLE_SUBMISSION.PROGRAM_ID.eq(programId)) - .and(IMPORTER_IMPORT.PROGRAM_ID.eq(programId)) - .and(IMPORTER_PROGRESS.STATUSCODE.eq((short) HttpStatus.OK.getCode())) - .orderBy(IMPORTER_IMPORT.CREATED_AT.desc()) - .fetch(record -> GenotypeImportDetails.builder() - .sampleSubmissionId(record.get(SAMPLE_SUBMISSION.ID)) - .projectNameForSampleSubmission(record.get(SAMPLE_SUBMISSION.NAME)) - .sampleSubmissionCreatedBy(record.get(sampleSubmissionCreatedByUser.NAME)) - .genotypingFileName(record.get(IMPORTER_IMPORT.UPLOAD_FILE_NAME)) - .genotypingImportDate(record.get(IMPORTER_IMPORT.CREATED_AT)) - .genotypingImportBy(record.get(genotypingImportByUser.NAME)) - .build()); - } - private void createGenotypeImportLink(UUID submissionId, UUID importerImportId, UUID userId) { - OffsetDateTime now = OffsetDateTime.now(); - log.debug("Inserting record into GenotypeImport table for submissionId={}, importerImportId={}, userId={}", submissionId, importerImportId, userId); - dsl.insertInto(GENOTYPE_IMPORT) - .set(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID, submissionId) - .set(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID, importerImportId) - .set(GENOTYPE_IMPORT.CREATED_AT, now) - .set(GENOTYPE_IMPORT.UPDATED_AT, now) - .set(GENOTYPE_IMPORT.CREATED_BY, userId) - .set(GENOTYPE_IMPORT.UPDATED_BY, userId) - .execute(); + return genotypeImportDAO.getGenotypeImportsByProgramId(programId); } private boolean validateSamples(Program program, UUID submissionId, byte[] fileContents, ImportUpload upload) throws DoesNotExistException, ApiException { @@ -539,7 +496,7 @@ protected void processSubmission(String gigwaAuthToken, Program program, UUID su if (checkGigwaProgress(client, gigwaAuthToken, gigwaProgressToken, progress)) { log.debug("Gigwa import was successful!"); //logic to add record to the new JOIN table - createGenotypeImportLink(submissionId, upload.getId(), upload.getCreatedBy()); + genotypeImportDAO.createGenotypeImportLink(submissionId, upload.getId(), upload.getCreatedBy()); progress.setMessage("Import successful"); progress.setStatuscode((short) HttpStatus.OK.getCode()); importDAO.updateProgress(progress); diff --git a/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java index bc1e291aa..3193a91a6 100644 --- a/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java +++ b/src/main/java/org/breedinginsight/utilities/response/mappers/GenotypeImportQueryMapper.java @@ -1,3 +1,20 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.breedinginsight.utilities.response.mappers; import lombok.Getter; diff --git a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java index ca4321b9d..7acf51a28 100644 --- a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java +++ b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java @@ -5,11 +5,7 @@ import com.agorapulse.micronaut.amazon.awssdk.s3.SimpleStorageServiceConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.context.ApplicationContext; -import io.micronaut.context.annotation.Context; -import io.micronaut.context.annotation.Factory; -import io.micronaut.context.annotation.Property; -import io.micronaut.context.annotation.Replaces; -import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.*; import io.micronaut.context.event.BeanCreatedEventListener; import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; @@ -49,6 +45,7 @@ import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; import org.breedinginsight.dao.db.tables.pojos.ImporterImportEntity; +import org.breedinginsight.daos.GenotypeImportDAO; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.SampleSubmissionDAO; import org.breedinginsight.daos.UserDAO; @@ -129,6 +126,9 @@ public class GigwaGenotypeServiceImplIntegrationTest extends DatabaseTest { @Inject private BrAPIGermplasmDAO germplasmDAO; + @Inject + private GenotypeImportDAO genotypeImportDAO; + @Inject private ObjectMapper objectMapper; @@ -222,6 +222,17 @@ BrAPIGermplasmDAO germplasmDAO() { } } + @Factory + @Requires(property = "micronaut.test.active.spec", value = "org.breedinginsight.services.geno.impl.GigwaGenotypeServiceImplIntegrationTest") + static class GenotypeImportDaoTestFactory { + + @Singleton + @Replaces(GenotypeImportDAO.class) + GenotypeImportDAO genotypeImportDAO() { + return mock(GenotypeImportDAO.class); + } + } + private GenericContainer gigwa; private GenericContainer mongo; @@ -254,12 +265,12 @@ public GigwaGenotypeServiceImplIntegrationTest() { .withEnv("GIGWA.serversAllowedToImport", gigwaAllowedServer) .waitingFor( Wait.forHttp("/gigwa") - .forStatusCode(200) - .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); + .forStatusCode(200) + .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); gigwa.start(); localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack") - .withTag("3.0.2")) + .withTag("3.0.2")) .withServices(LocalStackContainer.Service.S3) .withNetwork(super.getNetwork()) .withNetworkAliases("localstack") @@ -272,7 +283,7 @@ public GigwaGenotypeServiceImplIntegrationTest() { public Map getProperties() { Map properties = super.getProperties(); - properties.put("gigwa.host", "http://"+gigwa.getContainerIpAddress()+":"+gigwa.getMappedPort(8080)+"/"); + properties.put("gigwa.host", "http://" + gigwa.getContainerIpAddress() + ":" + gigwa.getMappedPort(8080) + "/"); properties.put("gigwa.username", "gigwadmin"); properties.put("gigwa.password", "nimda"); @@ -300,7 +311,12 @@ public void setup() throws IllegalAccessException, NoSuchFieldException { storageService = applicationContext.getBean(SimpleStorageService.class, Qualifiers.byName("genotype")); storageService.createBucket(); - } + } + + @BeforeEach + public void resetGenotypeImportDaoMock() { + reset(genotypeImportDAO); + } @AfterAll public void teardown() { @@ -318,36 +334,37 @@ public void testUpload() throws ApiException, AuthorizationException { assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> uploadGenoData(programId, programKey, submissionId, importId), "Upload did not complete within the time period"); assertTrue(storageService.exists(storageService.getDefaultBucketName(), programId + "/" + submissionId + "/" + importId + ".vcf"), "File was not uploaded to s3"); + verify(genotypeImportDAO).createGenotypeImportLink(eq(submissionId), eq(importId), any(UUID.class)); BrAPIClient brAPIClient = new BrAPIClient(gigwaHost + "gigwa/rest/brapi/v2"); Authentication authorizationToken = brAPIClient.getAuthentication("AuthorizationToken"); - if(authorizationToken instanceof OAuth) { - ((OAuth)authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); + if (authorizationToken instanceof OAuth) { + ((OAuth) authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); } ProgramsApi programsApi = new ProgramsApi(brAPIClient); try { ApiResponse brAPIProgramListResponseApiResponse = programsApi.programsGet(ProgramQueryParams.builder() - .programDbId(programKey) - .build()); + .programDbId(programKey) + .build()); assertEquals(1, - brAPIProgramListResponseApiResponse.getBody() - .getResult() - .getData() - .size()); + brAPIProgramListResponseApiResponse.getBody() + .getResult() + .getData() + .size()); StudiesApi studiesApi = new StudiesApi(brAPIClient); ApiResponse brAPIStudyListResponseApiResponse = studiesApi.studiesGet(StudyQueryParams.builder() - .build()); + .build()); assertEquals(1, - brAPIStudyListResponseApiResponse.getBody() - .getResult() - .getData() - .stream() - .filter(brAPIStudy -> brAPIStudy.getStudyName() - .equals(submissionId.toString())) - .count()); + brAPIStudyListResponseApiResponse.getBody() + .getResult() + .getData() + .stream() + .filter(brAPIStudy -> brAPIStudy.getStudyName() + .equals(submissionId.toString())) + .count()); } catch (ApiException e) { System.err.println(e.getMessage()); System.err.println(e.getResponseBody()); @@ -369,13 +386,13 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep SamplesApi mockSamplesApi = spy(new SamplesApi()); BrAPISample sample = new BrAPISample().sampleName(sampleName) - .germplasmDbId(programKey + "§" + sampleName); + .germplasmDbId(programKey + "§" + sampleName); doReturn(List.of(sample)).when(sampleDAO) - .readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); + .readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); doReturn(new ApiResponse<>(200, - new HashMap<>(), - Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), - Optional.empty()))) + new HashMap<>(), + Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), + Optional.empty()))) .when(mockSamplesApi).searchSamplesPost(any(BrAPISampleSearchRequest.class)); doReturn(mockSamplesApi).when(brAPIEndpointProvider).get(any(BrAPIClient.class), eq(SamplesApi.class)); @@ -391,8 +408,8 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep verify(sampleDAO).readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); verify(brAPIEndpointProvider, never()).get(any(BrAPIClient.class), eq(ObservationUnitsApi.class)); verify(mockSamplesApi).searchSamplesPost(argThat(searchRequest -> searchRequest.getGermplasmDbIds() != null && - searchRequest.getGermplasmDbIds().contains(programKey + "§" + sample.getSampleName()) && - (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); + searchRequest.getGermplasmDbIds().contains(programKey + "§" + sample.getSampleName()) && + (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); assertNotNull(germplasmGenotype); assertFalse(germplasmGenotype.getCalls().isEmpty()); assertFalse(germplasmGenotype.getCallSets().isEmpty()); @@ -419,44 +436,33 @@ public void testSubmitValidFile() throws IOException, ApiException { assertEquals((short) HttpStatus.ACCEPTED.getCode(), response.getProgress().getStatuscode(), "Error importing geno file: " + response.getProgress().getMessage()); - - Integer joinRowCount = dsl.fetchOne( - "select count(*) from genotype_import where sample_submission_id = ?::uuid", - submissionId - ).into(Integer.class); - assertEquals(1, joinRowCount); } @Test - public void testGetGenotypeImportsReturnsRowsIncludingSampleSubmissionId() throws Exception { + public void testGetGenotypeImportsReturnsRowsIncludingSampleSubmissionId() { UUID programId = UUID.randomUUID(); - String programKey = "TESTGETGENOIMPORTS"; UUID olderSubmissionId = UUID.randomUUID(); UUID newerSubmissionId = UUID.randomUUID(); - List samples = buildSamplesFromValidVcf(); - - setupMocksForSubmitGenoData(programId, olderSubmissionId, samples); - AtomicReference olderImportResponse = new AtomicReference<>(); - assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> - olderImportResponse.set(submitGenoData(programId, programKey, olderSubmissionId, "sample.vcf")), - "First submit did not complete within the time period"); - - assertNotNull(olderImportResponse.get()); - assertNotNull(olderImportResponse.get().getProgress()); - assertEquals((short) HttpStatus.ACCEPTED.getCode(), olderImportResponse.get().getProgress().getStatuscode()); - - Thread.sleep(10L); // keeps created_at ordering deterministic + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(olderSubmissionId) + .projectNameForSampleSubmission("Submission " + olderSubmissionId) + .sampleSubmissionCreatedBy("system") + .genotypingFileName("sample.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("system") + .build(); - setupMocksForSubmitGenoData(programId, newerSubmissionId, samples); - AtomicReference newerImportResponse = new AtomicReference<>(); - assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> - newerImportResponse.set(submitGenoData(programId, programKey, newerSubmissionId, "sample.vcf")), - "Second submit did not complete within the time period"); + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(newerSubmissionId) + .projectNameForSampleSubmission("Submission " + newerSubmissionId) + .sampleSubmissionCreatedBy("system") + .genotypingFileName("sample.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("system") + .build(); - assertNotNull(newerImportResponse.get()); - assertNotNull(newerImportResponse.get().getProgress()); - assertEquals((short) HttpStatus.ACCEPTED.getCode(), newerImportResponse.get().getProgress().getStatuscode()); + doReturn(List.of(newer, older)).when(genotypeImportDAO).getGenotypeImportsByProgramId(programId); List rows = gigwaGenoStorageService.getGenotypeImports(programId); @@ -476,16 +482,20 @@ public void testGetGenotypeImportsReturnsRowsIncludingSampleSubmissionId() throw assertNotNull(rows.get(1).getGenotypingImportDate()); assertEquals("system", rows.get(1).getSampleSubmissionCreatedBy()); assertEquals("system", rows.get(1).getGenotypingImportBy()); + + verify(genotypeImportDAO).getGenotypeImportsByProgramId(programId); } @Test public void testGetGenotypeImportsReturnsEmptyListWhenNoImportsExist() { UUID programId = UUID.randomUUID(); + doReturn(Collections.emptyList()).when(genotypeImportDAO).getGenotypeImportsByProgramId(programId); List rows = gigwaGenoStorageService.getGenotypeImports(programId); assertNotNull(rows); assertTrue(rows.isEmpty()); + verify(genotypeImportDAO).getGenotypeImportsByProgramId(programId); } @Test @@ -500,7 +510,7 @@ public void testSubmitInvalidHeader() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("Header row is not valid VCF format", response.getProgress().getMessage()); } @@ -518,7 +528,7 @@ public void testSubmitMissingSubmissionSamples() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("There are samples that are not linked to the selected submission", response.getProgress().getMessage()); } @@ -586,27 +596,6 @@ private void uploadGenoData(UUID programId, String programKey, UUID submissionId private ImportResponse submitGenoData(UUID programId, String programKey, UUID submissionId, String file) throws AuthorizationException, IOException, ApiException, DoesNotExistException { - - UUID userId = dsl.fetchOne("select id from bi_user where name = 'system' limit 1").into(UUID.class); - UUID speciesId = dsl.fetchOne("select id from species limit 1").into(UUID.class); - UUID mappingId = dsl.fetchOne("select id from importer_mapping where name = 'GenotypicDataImport' limit 1").into(UUID.class); - - assertNotNull(userId); - assertNotNull(speciesId); - assertNotNull(mappingId); - - dsl.execute( - "insert into program (id, species_id, name, abbreviation, documentation_url, objective, key, created_by, updated_by) " + - "values (?::uuid, ?::uuid, ?, ?, ?, ?, ?, ?::uuid, ?::uuid) " + - "on conflict (id) do nothing", - programId, speciesId, "Submit Geno Program", programKey, "localhost:8080", "test", programKey, userId, userId); - - dsl.execute( - "insert into sample_submission (id, name, program_id, created_by, updated_by) " + - "values (?::uuid, ?, ?::uuid, ?::uuid, ?::uuid) " + - "on conflict (id) do nothing", - submissionId, "Submission " + submissionId, programId, userId, userId); - Program program = Program.builder() .id(programId) .key(programKey) @@ -615,35 +604,25 @@ private ImportResponse submitGenoData(UUID programId, String programKey, UUID su doReturn(List.of(program)).when(programDAO).get(any(UUID.class)); User user = User.builder() - .id(userId) + .id(UUID.randomUUID()) .build(); doReturn(Optional.of(user)).when(userDAO).getUser(any(UUID.class)); ImportMapping mapping = ImportMapping.builder() - .id(mappingId) + .id(UUID.randomUUID()) .build(); doReturn(List.of(mapping)).when(importMappingDAO).getSystemMappingByName(any(String.class)); + doAnswer(invocation -> { + ImportProgress progress = invocation.getArgument(0, ImportProgress.class); + progress.setId(UUID.randomUUID()); + return null; + }).when(importDAO).createProgress(any(ImportProgress.class)); + UUID importId = UUID.randomUUID(); doAnswer(invocation -> { ImporterImportEntity importEntity = invocation.getArgument(0, ImporterImportEntity.class); importEntity.setId(importId); - - dsl.execute( - "insert into importer_import " + - "(id, program_id, user_id, importer_mapping_id, upload_file_name, created_at, updated_at, created_by, updated_by) " + - "values (?::uuid, ?::uuid, ?::uuid, ?::uuid, ?, ?::timestamptz, ?::timestamptz, ?::uuid, ?::uuid)", - importId, - importEntity.getProgramId(), - userId, - mappingId, - importEntity.getUploadFileName(), - importEntity.getCreatedAt(), - importEntity.getUpdatedAt(), - userId, - userId - ); - return null; }).when(importDAO).insert(any(ImporterImportEntity.class)); From a88a71206132ad1e0011a394f948bd02b4bf6066 Mon Sep 17 00:00:00 2001 From: Keerthi Humsika Kattamudi Date: Mon, 15 Jun 2026 11:27:03 -0500 Subject: [PATCH 6/6] BI-2848: Added GenotypeImportDAO and also Rebased with develop branch to get the BI-2782 changes. --- .../daos/GenotypeImportDAO.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java diff --git a/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java b/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java new file mode 100644 index 000000000..c34f7baae --- /dev/null +++ b/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java @@ -0,0 +1,85 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.breedinginsight.daos; + +import io.micronaut.http.HttpStatus; +import org.breedinginsight.dao.db.tables.BiUserTable; +import org.breedinginsight.dao.db.tables.daos.GenotypeImportDao; +import org.breedinginsight.dao.db.tables.pojos.GenotypeImportEntity; +import org.breedinginsight.model.GenotypeImportDetails; +import org.jooq.Configuration; +import org.jooq.DSLContext; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +import static org.breedinginsight.dao.db.Tables.*; + +@Singleton +public class GenotypeImportDAO extends GenotypeImportDao { + + private final DSLContext dsl; + + @Inject + public GenotypeImportDAO(Configuration config, DSLContext dsl) { + super(config); + this.dsl = dsl; + } + + public void createGenotypeImportLink(UUID submissionId, UUID importerImportId, UUID userId) { + OffsetDateTime now = OffsetDateTime.now(); + + insert(GenotypeImportEntity.builder() + .id(UUID.randomUUID()) + .sampleSubmissionId(submissionId) + .importerImportId(importerImportId) + .createdAt(now) + .updatedAt(now) + .createdBy(userId) + .updatedBy(userId) + .build()); + } + + public List getGenotypeImportsByProgramId(UUID programId) { + BiUserTable sampleSubmissionCreatedByUser = BI_USER.as("sampleSubmissionCreatedByUser"); + BiUserTable genotypingImportByUser = BI_USER.as("genotypingImportByUser"); + + return dsl.select( + SAMPLE_SUBMISSION.ID, + SAMPLE_SUBMISSION.NAME, + sampleSubmissionCreatedByUser.NAME, + IMPORTER_IMPORT.UPLOAD_FILE_NAME, + IMPORTER_IMPORT.CREATED_AT, + genotypingImportByUser.NAME) + .from(GENOTYPE_IMPORT) + .join(SAMPLE_SUBMISSION).on(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID.eq(SAMPLE_SUBMISSION.ID)) + .join(sampleSubmissionCreatedByUser).on(SAMPLE_SUBMISSION.CREATED_BY.eq(sampleSubmissionCreatedByUser.ID)) + .join(IMPORTER_IMPORT).on(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID.eq(IMPORTER_IMPORT.ID)) + .join(IMPORTER_PROGRESS).on(IMPORTER_IMPORT.IMPORTER_PROGRESS_ID.eq(IMPORTER_PROGRESS.ID)) + .join(genotypingImportByUser).on(IMPORTER_IMPORT.USER_ID.eq(genotypingImportByUser.ID)) + .where(SAMPLE_SUBMISSION.PROGRAM_ID.eq(programId)) + .and(IMPORTER_IMPORT.PROGRAM_ID.eq(programId)) + .and(IMPORTER_PROGRESS.STATUSCODE.eq((short) HttpStatus.OK.getCode())) + .orderBy(IMPORTER_IMPORT.CREATED_AT.desc()) + .fetch(record -> GenotypeImportDetails + .parseSqlRecord(record, sampleSubmissionCreatedByUser, genotypingImportByUser)); + } + +} \ No newline at end of file