Skip to content

Commit f3facd9

Browse files
Merge pull request #486 from Breeding-Insight/release/1.1.1
Merge release/1.1.1 into main
2 parents bdaa5b2 + 7112d7e commit f3facd9

24 files changed

Lines changed: 360 additions & 108 deletions

.env.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ BRAPI_READ_TIMEOUT=60m
3131
# Max number of records to POST to the BrAPI service per request
3232
POST_CHUNK_SIZE=1000
3333

34+
# Request cache records paginating through available records per program by CACHE_BRAPI_FETCH_PAGE_SIZE
35+
CACHE_PAGINATE_GERMPLASM=false
36+
CACHE_BRAPI_FETCH_PAGE_SIZE=65000
37+
3438
# BrAPI Server Variables
3539
BRAPI_SERVER_PORT=8083
3640

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ services:
7171
networks:
7272
- backend
7373
bidb:
74-
image: postgres:11.4
74+
image: postgres:17.5
7575
container_name: bidb
7676
environment:
7777
- POSTGRES_DB=${DB_NAME}

src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,26 @@ public HttpResponse<Response<DataResponse<List<BrAPIGermplasm>>>> getGermplasm(
222222
}
223223
}
224224

225+
@Get("/programs/{programId}/germplasm/lists/{listDbId}/export{?fileExtension}")
226+
@Produces(value = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
227+
@ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES})
228+
public HttpResponse<StreamedFile> germplasmListExport(
229+
@PathVariable("programId") UUID programId, @PathVariable("listDbId") String listDbId, @QueryValue(defaultValue = "XLSX") String fileExtension) {
230+
String downloadErrorMessage = "An error occurred while generating the download file. Contact the development team at bidevteam@cornell.edu.";
231+
try {
232+
FileType extension = Enum.valueOf(FileType.class, fileExtension);
233+
DownloadFile germplasmListFile = germplasmService.exportGermplasmList(programId, listDbId, extension);
234+
HttpResponse<StreamedFile> germplasmListExport = HttpResponse.ok(germplasmListFile.getStreamedFile()).header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename="+germplasmListFile.getFileName()+extension.getExtension());
235+
return germplasmListExport;
236+
}
237+
catch (Exception e) {
238+
log.info(e.getMessage(), e);
239+
e.printStackTrace();
240+
HttpResponse response = HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, downloadErrorMessage).contentType(MediaType.TEXT_PLAIN).body(downloadErrorMessage);
241+
return response;
242+
}
243+
}
244+
225245
@Get("/programs/{programId}/germplasm/export{?fileExtension,list}")
226246
@Produces(value = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
227247
@ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES})

src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIGermplasmDAO.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public class BrAPIGermplasmDAO {
6565
@Property(name = "micronaut.bi.api.run-scheduled-tasks")
6666
private boolean runScheduledTasks;
6767

68+
@Property(name = "brapi.paginate.germplasm")
69+
private boolean paginateGermplasm;
70+
6871
private final ProgramCache<BrAPIGermplasm> programGermplasmCache;
6972

7073
private final BrAPIEndpointProvider brAPIEndpointProvider;
@@ -141,11 +144,26 @@ private Map<String, BrAPIGermplasm> fetchProgramGermplasm(UUID programId) throws
141144
BrAPIGermplasmSearchRequest germplasmSearch = new BrAPIGermplasmSearchRequest();
142145
germplasmSearch.externalReferenceIDs(List.of(programId.toString()));
143146
germplasmSearch.externalReferenceSources(List.of(Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.PROGRAMS)));
144-
return processGermplasmForDisplay(brAPIDAOUtil.search(
145-
api::searchGermplasmPost,
146-
api::searchGermplasmSearchResultsDbIdGet,
147-
germplasmSearch
148-
), program.getKey());
147+
148+
if (paginateGermplasm) {
149+
log.debug("Fetching germplasm with pagination to BrAPI");
150+
return processGermplasmForDisplay(brAPIDAOUtil.search(
151+
api::searchGermplasmPost,
152+
api::searchGermplasmSearchResultsDbIdGet,
153+
germplasmSearch),
154+
program.getKey());
155+
} else {
156+
log.debug("Fetching germplasm without pagination to BrAPI");
157+
return processGermplasmForDisplay(brAPIDAOUtil.searchNoPaging(
158+
api::searchGermplasmPost,
159+
api::searchGermplasmSearchResultsDbIdGet,
160+
germplasmSearch),
161+
program.getKey());
162+
}
163+
}
164+
165+
public void repopulateGermplasmCacheForProgram(UUID programId) {
166+
programGermplasmCache.populate(programId);
149167
}
150168

151169
/**
@@ -296,11 +314,8 @@ public List<BrAPIGermplasm> createBrAPIGermplasm(List<BrAPIGermplasm> postBrAPIG
296314
var program = programDAO.fetchOneById(programId);
297315
try {
298316
if (!postBrAPIGermplasmList.isEmpty()) {
299-
Callable<Map<String, BrAPIGermplasm>> postFunction = () -> {
300317
List<BrAPIGermplasm> postResponse = brAPIDAOUtil.post(postBrAPIGermplasmList, upload, api::germplasmPost, importDAO::update);
301-
return processGermplasmForDisplay(postResponse, program.getKey());
302-
};
303-
return programGermplasmCache.post(programId, postFunction);
318+
return new ArrayList<>(processGermplasmForDisplay(postResponse, program.getKey()).values());
304319
}
305320
return new ArrayList<>();
306321
} catch (Exception e) {

src/main/java/org/breedinginsight/brapps/importer/model/base/Germplasm.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,19 @@ public static String constructGermplasmListName(String listName, Program program
155155
return String.format("%s [%s-germplasm]", listName, program.getKey());
156156
}
157157

158-
public void updateBrAPIGermplasm(BrAPIGermplasm germplasm, Program program, UUID listId, boolean commit, boolean updatePedigree) {
158+
/**
159+
* Will mutate synonym and pedigree fields if changed and meet change criteria
160+
*
161+
* @param germplasm germplasm object
162+
* @param program program
163+
* @param listId list id
164+
* @param commit flag indicating if commit changes should be made
165+
* @param updatePedigree flag indicating if pedigree should be updated
166+
* @return mutated indicator
167+
*/
168+
public boolean updateBrAPIGermplasm(BrAPIGermplasm germplasm, Program program, UUID listId, boolean commit, boolean updatePedigree) {
169+
170+
boolean mutated = false;
159171

160172
if (updatePedigree) {
161173
if (!StringUtils.isBlank(getFemaleParentAccessionNumber())) {
@@ -170,6 +182,7 @@ public void updateBrAPIGermplasm(BrAPIGermplasm germplasm, Program program, UUID
170182
if (!StringUtils.isBlank(getMaleParentEntryNo())) {
171183
germplasm.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_MALE_PARENT_ENTRY_NO, getMaleParentEntryNo());
172184
}
185+
mutated = true;
173186
}
174187

175188
// Append synonyms to germplasm that don't already exist
@@ -181,6 +194,7 @@ public void updateBrAPIGermplasm(BrAPIGermplasm germplasm, Program program, UUID
181194
brapiSynonym.setSynonym(synonym);
182195
if (!existingSynonyms.contains(brapiSynonym)) {
183196
germplasm.addSynonymsItem(brapiSynonym);
197+
mutated = true;
184198
}
185199
}
186200
}
@@ -193,6 +207,8 @@ public void updateBrAPIGermplasm(BrAPIGermplasm germplasm, Program program, UUID
193207
if (commit) {
194208
setUpdateCommitFields(germplasm, program.getKey());
195209
}
210+
211+
return mutated;
196212
}
197213

198214

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/factory/data/OverwrittenData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public PendingImportObject<BrAPIObservation> constructPendingObservation() {
135135
original = observation.getValue();
136136
}
137137

138-
if (!isTimestampMatched()) {
138+
if (!isTimestampMatched() && StringUtils.isNotBlank(timestamp)) {
139139
// Update the timestamp
140140
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
141141
String formattedTimeStampValue = formatter.format(observationService.parseDateTime(timestamp));

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/middleware/process/ImportTableProcess.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public AppendOverwriteMiddlewareContext process(AppendOverwriteMiddlewareContext
284284
BrAPIObservation observation = gson.fromJson(gson.toJson(observationByObsHash.get(observationHash)), BrAPIObservation.class);
285285

286286
// Is there a change to the prior data?
287-
if (isChanged(cellData, observation, cell.timestamp)) {
287+
if (isChanged(cellData, observation, cell.timestamp, tsColByPheno.containsKey(phenoColumnName))) {
288288

289289
// Is prior data protected?
290290
/**
@@ -394,14 +394,15 @@ public AppendOverwriteMiddlewareContext process(AppendOverwriteMiddlewareContext
394394
}
395395
}
396396

397-
private boolean isChanged(String cellData, BrAPIObservation observation, String newTimestamp) {
397+
private boolean isChanged(String cellData, BrAPIObservation observation, String newTimestamp, boolean timestampColumnPresent) {
398398
if (!cellData.isBlank() && !cellData.equals(observation.getValue())){
399399
return true;
400400
}
401-
if (StringUtils.isBlank(newTimestamp)) {
402-
return (observation.getObservationTimeStamp()!=null);
401+
// Only check timestamp if the TS:<trait> column was present in the uploaded file and there's a valid timestamp.
402+
if (timestampColumnPresent && !StringUtils.isBlank(newTimestamp)) {
403+
return !observationService.parseDateTime(newTimestamp).equals(observation.getObservationTimeStamp());
403404
}
404-
return !observationService.parseDateTime(newTimestamp).equals(observation.getObservationTimeStamp());
405+
return false;
405406
}
406407

407408
/**

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/ObservationService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.breedinginsight.brapps.importer.services.processors.experiment.service;
1919

20+
import lombok.extern.slf4j.Slf4j;
2021
import org.apache.commons.codec.digest.DigestUtils;
2122
import org.apache.commons.lang3.StringUtils;
2223
import org.brapi.v2.model.core.BrAPISeason;
@@ -42,6 +43,7 @@
4243
import java.util.*;
4344
import java.util.stream.Collectors;
4445

46+
@Slf4j
4547
@Singleton
4648
public class ObservationService {
4749
private final ExperimentUtilities experimentUtilities;
@@ -115,6 +117,7 @@ public OffsetDateTime parseDateTime(String dateString) {
115117
LocalDate localDate = LocalDate.parse(dateString, formatter);
116118
return localDate.atStartOfDay().atOffset(ZoneOffset.UTC);
117119
} catch (DateTimeParseException ex) {
120+
log.error("Failed to parse timestamp: \"{}\".", dateString);
118121
// If both parsing attempts fail, return null
119122
return null;
120123
}

src/main/java/org/breedinginsight/brapps/importer/services/processors/germplasm/GermplasmProcessor.java

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public class GermplasmProcessor implements Processor {
8787
List<List<BrAPIGermplasm>> postOrder = new ArrayList<>();
8888
BrAPIListNewRequest importList = new BrAPIListNewRequest();
8989

90+
private int numNewPedigreeConnections = 0;
91+
9092
public static String missingGIDsMsg = "The following GIDs were not found in the database: %s";
9193
public static String missingParentalGIDsMsg = "The following parental GIDs were not found in the database: %s";
9294
public static String missingParentalEntryNoMsg = "The following parental entry numbers were not found in the database: %s";
@@ -332,7 +334,7 @@ public Map<String, ImportPreviewStatistics> process(ImportUpload upload, List<Br
332334
createPostOrder();
333335

334336
// Construct our response object
335-
return getStatisticsMap(importRows);
337+
return getStatisticsMap();
336338
}
337339

338340
private void processNewGermplasm(Germplasm germplasm, ValidationErrors validationErrors, Map<String, ProgramBreedingMethodEntity> breedingMethods,
@@ -359,6 +361,10 @@ private void processNewGermplasm(Germplasm germplasm, ValidationErrors validatio
359361

360362
validatePedigree(germplasm, i + 2, validationErrors);
361363

364+
if (germplasm.pedigreeExists()) {
365+
numNewPedigreeConnections++;
366+
}
367+
362368
BrAPIGermplasm newGermplasm = germplasm.constructBrAPIGermplasm(program, breedingMethod, user, commit, BRAPI_REFERENCE_SOURCE, nextVal, importListId);
363369

364370
newGermplasmList.add(newGermplasm);
@@ -383,6 +389,9 @@ private Germplasm removeBreedingMethodBlanks(Germplasm germplasm) {
383389
private boolean processExistingGermplasm(Germplasm germplasm, ValidationErrors validationErrors, List<BrAPIImport> importRows, Program program, UUID importListId, boolean commit, PendingImport mappedImportRow, int rowIndex) {
384390
BrAPIGermplasm existingGermplasm;
385391
String gid = germplasm.getAccessionNumber();
392+
boolean mutated = false;
393+
boolean updatePedigree = false;
394+
386395
if (germplasmByAccessionNumber.containsKey(gid)) {
387396
existingGermplasm = germplasmByAccessionNumber.get(gid).getBrAPIObject();
388397
// Serialize and deserialize to deep copy
@@ -408,17 +417,26 @@ private boolean processExistingGermplasm(Germplasm germplasm, ValidationErrors v
408417
}
409418
}
410419

411-
if(germplasm.pedigreeExists()) {
420+
// if no existing pedigree and file has pedigree then validate and update
421+
if(germplasm.pedigreeExists() && !hasPedigree(existingGermplasm)) {
412422
validatePedigree(germplasm, rowIndex + 2, validationErrors);
423+
updatePedigree = true;
413424
}
414425

415-
germplasm.updateBrAPIGermplasm(existingGermplasm, program, importListId, commit, true);
416-
417-
updatedGermplasmList.add(existingGermplasm);
418-
mappedImportRow.setGermplasm(new PendingImportObject<>(ImportObjectState.MUTATED, existingGermplasm));
419-
importList.addDataItem(existingGermplasm.getGermplasmName());
426+
mutated = germplasm.updateBrAPIGermplasm(existingGermplasm, program, importListId, commit, updatePedigree);
420427

428+
if (mutated) {
429+
updatedGermplasmList.add(existingGermplasm);
430+
mappedImportRow.setGermplasm(new PendingImportObject<>(ImportObjectState.MUTATED, existingGermplasm));
431+
if (updatePedigree) {
432+
numNewPedigreeConnections++;
433+
}
434+
} else {
435+
mappedImportRow.setGermplasm(new PendingImportObject<>(ImportObjectState.EXISTING, existingGermplasm));
436+
}
421437

438+
// add to list regardless of mutated or not
439+
importList.addDataItem(existingGermplasm.getGermplasmName());
422440
return true;
423441
}
424442

@@ -521,20 +539,17 @@ private boolean canUpdatePedigreeNoEqualsCheck(BrAPIGermplasm existingGermplasm,
521539
germplasm.pedigreeExists();
522540
}
523541

524-
private Map<String, ImportPreviewStatistics> getStatisticsMap(List<BrAPIImport> importRows) {
542+
private Map<String, ImportPreviewStatistics> getStatisticsMap() {
525543

526544
ImportPreviewStatistics germplasmStats = ImportPreviewStatistics.builder()
527545
.newObjectCount(newGermplasmList.size())
528546
.ignoredObjectCount(germplasmByAccessionNumber.size())
529547
.build();
530548

531-
//Modified logic here to check for female parent accession number or entry no, removed check for male due to assumption that shouldn't have only male parent
532-
int newObjectCount = newGermplasmList.stream().filter(newGermplasm -> newGermplasm != null).collect(Collectors.toList()).size();
549+
// TODO: numNewPedigreeConnections is global modified in existing and new flows, refactor at some point
533550
ImportPreviewStatistics pedigreeConnectStats = ImportPreviewStatistics.builder()
534-
.newObjectCount(importRows.stream().filter(germplasmImport ->
535-
germplasmImport.getGermplasm() != null &&
536-
(germplasmImport.getGermplasm().getFemaleParentAccessionNumber() != null || germplasmImport.getGermplasm().getFemaleParentEntryNo() != null)
537-
).collect(Collectors.toList()).size()).build();
551+
.newObjectCount(numNewPedigreeConnections)
552+
.build();
538553

539554
return Map.of(
540555
"Germplasm", germplasmStats,
@@ -631,10 +646,13 @@ public void postBrapiData(Map<Integer, PendingImport> mappedBrAPIImport, Program
631646
}
632647

633648
// Create list
634-
if (!newGermplasmList.isEmpty() || !updatedGermplasmList.isEmpty()) {
649+
// create & update flows both unconditionally add germplasm names to importList so use that for check
650+
if (!importList.getData().isEmpty()) {
635651
try {
636652
// Create germplasm list
637653
brAPIListDAO.createBrAPILists(List.of(importList), program.getId(), upload);
654+
// Now that we have finished uploading, fetch all the data posted to BrAPI to the cache so it is up-to-date.
655+
brAPIGermplasmDAO.repopulateGermplasmCacheForProgram(program.getId());
638656
} catch (ApiException e) {
639657
throw new InternalServerException(e.toString(), e);
640658
}

0 commit comments

Comments
 (0)