Skip to content

Commit 686568c

Browse files
authored
Merge pull request #442 from Breeding-Insight/BI-2156-qa
BI-2156-qa - Append Workflow Edge Cases
2 parents a3e6fc5 + b4f1a24 commit 686568c

File tree

14 files changed

+306
-60
lines changed

14 files changed

+306
-60
lines changed

src/main/java/org/breedinginsight/brapps/importer/services/FileImportService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ private void processFile(String workflowId, List<BrAPIImport> finalBrAPIImportLi
449449
progress.setMessage(e.getMessage());
450450
progress.setUpdatedBy(actingUser.getId());
451451
importDAO.update(upload);
452-
}catch (ValidatorException e) {
452+
} catch (ValidatorException e) {
453453
log.info("Validation errors: \n" + e);
454454
ImportProgress progress = upload.getProgress();
455455
progress.setStatuscode((short) HttpStatus.UNPROCESSABLE_ENTITY.getCode());

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

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.model.AppendOverwriteMiddlewareContext;
4545
import org.breedinginsight.brapps.importer.services.processors.experiment.create.model.PendingData;
4646
import org.breedinginsight.brapps.importer.services.processors.experiment.create.model.ProcessedPhenotypeData;
47+
import org.breedinginsight.brapps.importer.services.processors.experiment.model.EntityNotFoundException;
4748
import org.breedinginsight.brapps.importer.services.processors.experiment.model.ExpImportProcessConstants;
4849
import org.breedinginsight.model.Program;
4950
import org.breedinginsight.model.Scale;
@@ -70,8 +71,7 @@ public class ExperimentUtilities {
7071
public static final String PREEXISTING_EXPERIMENT_TITLE = "Experiment Title already exists";
7172
public static final String MISSING_OBS_UNIT_ID_ERROR = "Experimental entities are missing ObsUnitIDs";
7273
public static final String UNMATCHED_COLUMN = "Ontology term(s) not found: ";
73-
74-
74+
public static final String INVALID_OBS_UNIT_ID_ERROR = "Invalid ObsUnitID";
7575

7676

7777
Gson gson;
@@ -294,44 +294,104 @@ public static void addYearToStudyAdditionalInfo(Program program, BrAPIStudy stud
294294
}
295295

296296
/**
297-
* This method is responsible for collating unique ObsUnit IDs from the provided context data.
297+
* Collates unique Observation Unit IDs from the import context.
298+
*
299+
* This method iterates through all import rows in the given context and
300+
* extracts unique Observation Unit IDs (ObsUnit IDs) that are not null or blank.
301+
*
302+
* @param context The AppendOverwriteMiddlewareContext containing the import data.
303+
* @return A Set of String containing all unique, non-null, non-blank Observation Unit IDs.
298304
*
299-
* @param context the AppendOverwriteMiddlewareContext containing the import rows to process
300-
* @return a Set of unique ObsUnit IDs collated from the import rows
301-
* @throws IllegalStateException if any ObsUnit ID is repeated in the import rows
302-
* @throws HttpStatusException if there is a mix of ObsUnit IDs for some but not all rows
305+
* @implNote The method performs the following steps:
306+
* 1. Initializes an empty HashSet to store unique ObsUnit IDs.
307+
* 2. Iterates through each import row in the context.
308+
* 3. For each row, checks if the ObsUnit ID is not null and not blank.
309+
* 4. If valid, adds the ObsUnit ID to the set.
310+
* 5. Returns the set of unique ObsUnit IDs.
303311
*/
304-
public static Set<String> collateReferenceOUIds(AppendOverwriteMiddlewareContext context) throws HttpStatusException, IllegalStateException {
312+
public static Set<String> collateUniqueOUIds(AppendOverwriteMiddlewareContext context) {
305313
// Initialize variables to track the presence of ObsUnit IDs
306314
Set<String> referenceOUIds = new HashSet<>();
307-
boolean hasNoReferenceUnitIds = true;
308-
boolean hasAllReferenceUnitIds = true;
315+
316+
// Iterate through the import rows to process ObsUnit IDs
317+
for (int rowNum = 0; rowNum < context.getImportContext().getImportRows().size(); rowNum++) {
318+
ExperimentObservation importRow = (ExperimentObservation) context.getImportContext().getImportRows().get(rowNum);
319+
if (importRow.getObsUnitID() != null && !importRow.getObsUnitID().isBlank()) {
320+
referenceOUIds.add(importRow.getObsUnitID());
321+
}
322+
}
323+
return referenceOUIds;
324+
}
325+
326+
/**
327+
* Validates Observation Unit ID values in the import context.
328+
*
329+
* This method checks each import row for the validity of its Observation Unit ID (ObsUnitID).
330+
* It performs the following validations:
331+
* 1. Checks if the ObsUnitID is null or blank.
332+
* 2. Checks if the ObsUnitID is a duplicate within the import data.
333+
*
334+
* @param context The AppendOverwriteMiddlewareContext containing import data and validation error storage.
335+
* @throws HttpStatusException If there's an HTTP-related error during the validation process.
336+
* @throws IllegalStateException If the system encounters an unexpected state during validation.
337+
*
338+
* @implNote The method performs the following steps:
339+
* 1. Retrieves the ValidationErrors object from the context.
340+
* 2. Initializes a HashSet to track unique ObsUnitIDs.
341+
* 3. Iterates through each import row in the context.
342+
* 4. For each row:
343+
* - If ObsUnitID is null or blank, adds a "missing ObsUnitID" error.
344+
* - If ObsUnitID is already in the set (duplicate), adds a "duplicate ObsUnitID" error.
345+
* - Otherwise, adds the ObsUnitID to the set of unique IDs.
346+
* 5. Errors are added using the addRowError method, specifying the OBS_UNIT_ID column and appropriate error messages.
347+
*/
348+
public static void validateReferenceOUIdValues(AppendOverwriteMiddlewareContext context) throws HttpStatusException, IllegalStateException {
349+
ValidationErrors validationErrors = context.getAppendOverwriteWorkflowContext().getValidationErrors();
350+
Set<String> referenceOUIds = new HashSet<>();
309351

310352
// Iterate through the import rows to process ObsUnit IDs
311353
for (int rowNum = 0; rowNum < context.getImportContext().getImportRows().size(); rowNum++) {
312354
ExperimentObservation importRow = (ExperimentObservation) context.getImportContext().getImportRows().get(rowNum);
313355

314-
// Check if ObsUnitID is blank
315356
if (importRow.getObsUnitID() == null || importRow.getObsUnitID().isBlank()) {
316-
// Set flag to indicate missing ObsUnit ID for current row
317-
hasAllReferenceUnitIds = false;
357+
// Check if ObsUnitID is blank
358+
addRowError(ExperimentObservation.Columns.OBS_UNIT_ID, ExpImportProcessConstants.ErrMessage.MISSING_OBS_UNIT_ID.getValue(), validationErrors, rowNum);
318359
} else if (referenceOUIds.contains(importRow.getObsUnitID())) {
319-
// Throw exception if ObsUnitID is repeated
320-
throw new IllegalStateException("ObsUnitId is repeated: " + importRow.getObsUnitID());
360+
// Check if ObsUnitID is repeated
361+
addRowError(ExperimentObservation.Columns.OBS_UNIT_ID, ExpImportProcessConstants.ErrMessage.DUPLICATE_OBS_UNIT_ID.getValue(), validationErrors, rowNum);
321362
} else {
322363
// Add ObsUnitID to referenceOUIds
323364
referenceOUIds.add(importRow.getObsUnitID());
324-
// Set flag to indicate presence of ObsUnit ID
325-
hasNoReferenceUnitIds = false;
326365
}
327366
}
367+
}
328368

329-
if (!hasNoReferenceUnitIds && !hasAllReferenceUnitIds) {
330-
// Throw exception if there is a mix of ObsUnit IDs for some but not all rows
331-
throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, ExpImportProcessConstants.ErrMessage.MISSING_OBS_UNIT_ID_ERROR.getValue());
332-
}
369+
/**
370+
* Adds validation errors for observation units that were not found in the database.
371+
*
372+
* This method processes an EntityNotFoundException and adds corresponding validation errors
373+
* to the context for each import row where the Observation Unit ID was not found.
374+
*
375+
* @param e The EntityNotFoundException containing information about missing Observation Unit IDs.
376+
* @param context The AppendOverwriteMiddlewareContext containing import data and validation error storage.
377+
*
378+
* @implNote The method performs the following steps:
379+
* 1. Retrieves the ValidationErrors object from the context.
380+
* 2. Iterates through each import row in the context.
381+
* 3. For each row, checks if its Observation Unit ID is in the set of missing entity IDs from the exception.
382+
* 4. If a match is found, adds a validation error for that row, indicating an invalid Observation Unit ID.
383+
* 5. The error is added using the addRowError method, specifying the OBS_UNIT_ID column and using a predefined error message.
384+
*/
385+
public static void addValidationErrorsForObsUnitsNotFound(EntityNotFoundException e, AppendOverwriteMiddlewareContext context) {
386+
ValidationErrors validationErrors = context.getAppendOverwriteWorkflowContext().getValidationErrors();
387+
List<ValidationError> errors = new ArrayList<>();
333388

334-
return referenceOUIds;
389+
for (int rowNum = 0; rowNum < context.getImportContext().getImportRows().size(); rowNum++) {
390+
String rowObsUnitId = ((ExperimentObservation)context.getImportContext().getImportRows().get(rowNum)).getObsUnitID();
391+
if (e.getMissingEntityIds().contains(rowObsUnitId)) {
392+
addRowError(ExperimentObservation.Columns.OBS_UNIT_ID, ExperimentUtilities.INVALID_OBS_UNIT_ID_ERROR, validationErrors, rowNum);
393+
}
394+
}
335395
}
336396

337397
/**

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/factory/action/BrAPIAction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import org.brapi.client.v2.model.exceptions.ApiException;
2121
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.factory.BrAPIState;
2222
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.factory.entity.ExperimentImportEntity;
23+
import org.breedinginsight.brapps.importer.services.processors.experiment.model.EntityNotFoundException;
2324
import org.breedinginsight.services.exceptions.DoesNotExistException;
2425
import org.breedinginsight.services.exceptions.MissingRequiredInfoException;
2526
import org.breedinginsight.services.exceptions.UnprocessableEntityException;
27+
import org.breedinginsight.services.exceptions.ValidatorException;
2628

2729
import java.util.Optional;
2830

@@ -42,7 +44,7 @@ public interface BrAPIAction<T> {
4244
* @return An Optional containing the relevant BrAPI state after executing the action.
4345
* @throws ApiException if an error occurs during the execution of the action.
4446
*/
45-
Optional<BrAPIState<T>> execute() throws ApiException, MissingRequiredInfoException, UnprocessableEntityException, DoesNotExistException;
47+
Optional<BrAPIState<T>> execute() throws ApiException, MissingRequiredInfoException, UnprocessableEntityException, DoesNotExistException, EntityNotFoundException;
4648

4749
/**
4850
* Get the BrAPI entity being acted on based on the provided ExpUnitMiddlewareContext.

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/factory/action/WorkflowReadInitialization.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.brapi.client.v2.model.exceptions.ApiException;
2424
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.factory.BrAPIState;
2525
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.factory.entity.ExperimentImportEntity;
26+
import org.breedinginsight.brapps.importer.services.processors.experiment.model.EntityNotFoundException;
27+
import org.breedinginsight.services.exceptions.ValidatorException;
2628
import org.breedinginsight.utilities.Utilities;
2729

2830
import java.util.List;
@@ -44,7 +46,7 @@ protected WorkflowReadInitialization(ExperimentImportEntity<T> entity) {
4446
* @return an Optional containing the BrAPIState representing the completed read workflow
4547
* @throws ApiException if an error occurs during execution
4648
*/
47-
public Optional<BrAPIState<T>> execute() throws ApiException {
49+
public Optional<BrAPIState<T>> execute() throws ApiException, EntityNotFoundException {
4850
try {
4951
List<T> fetchedMembers = entity.brapiRead();
5052
entity.initializeWorkflow(fetchedMembers);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ public static EmptyData emptyData(String brapiReferenceSource,
112112
return new EmptyData(brapiReferenceSource, isCommit, germplasmName, study, phenoColumnName, trialId, studyId, unitId, studyYear, observationUnit, user, program, studyService, observationService);
113113
}
114114

115+
public static UndefinedDataset undefinedDataset() {
116+
return new UndefinedDataset();
117+
}
118+
115119
@Bean
116120
@Prototype
117121
public InitialData initialDataBean(String brapiReferenceSource,
@@ -173,5 +177,11 @@ public EmptyData emptyDataBean(String brapiReferenceSource,
173177
Program program) {
174178
return emptyData(brapiReferenceSource, isCommit, germplasmName, study, phenoColumnName, trialId, studyId, unitId, studyYear, observationUnit, user, program, studyService, observationService);
175179
}
180+
181+
@Bean
182+
@Prototype
183+
public UndefinedDataset undefinedDatasetBean() {
184+
return undefinedDataset();
185+
}
176186
}
177187

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional information
3+
* regarding copyright ownership.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.factory.data;
19+
20+
import org.brapi.v2.model.pheno.BrAPIObservation;
21+
import org.breedinginsight.api.model.v1.response.ValidationError;
22+
import org.breedinginsight.brapps.importer.model.response.PendingImportObject;
23+
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.middleware.process.AppendStatistic;
24+
25+
import java.util.List;
26+
import java.util.Optional;
27+
28+
public class UndefinedDataset extends VisitedObservationData {
29+
@Override
30+
public Optional<List<ValidationError>> getValidationErrors() {
31+
return Optional.empty();
32+
}
33+
34+
@Override
35+
public PendingImportObject<BrAPIObservation> constructPendingObservation() {
36+
return null;
37+
}
38+
39+
@Override
40+
public void updateTally(AppendStatistic statistic) {
41+
42+
}
43+
}

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/factory/entity/ExperimentImportEntity.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
import org.brapi.client.v2.model.exceptions.ApiException;
2121
import org.breedinginsight.brapps.importer.model.response.ImportObjectState;
22+
import org.breedinginsight.brapps.importer.services.processors.experiment.model.EntityNotFoundException;
2223
import org.breedinginsight.services.exceptions.DoesNotExistException;
2324
import org.breedinginsight.services.exceptions.MissingRequiredInfoException;
2425
import org.breedinginsight.services.exceptions.UnprocessableEntityException;
26+
import org.breedinginsight.services.exceptions.ValidatorException;
2527

2628
import java.util.List;
2729

@@ -43,7 +45,7 @@ public interface ExperimentImportEntity<T> {
4345
* @return List of fetched entities
4446
* @throws ApiException if there is an issue with the API call
4547
*/
46-
public List<T> brapiRead() throws ApiException;
48+
public List<T> brapiRead() throws ApiException, EntityNotFoundException;
4749

4850
/**
4951
* Commit objects changed by the workflow to the BrAPI service.

src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/appendoverwrite/factory/entity/PendingObservationUnit.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.breedinginsight.brapps.importer.model.response.ImportObjectState;
2727
import org.breedinginsight.brapps.importer.model.response.PendingImportObject;
2828
import org.breedinginsight.brapps.importer.services.processors.experiment.ExperimentUtilities;
29+
import org.breedinginsight.brapps.importer.services.processors.experiment.model.EntityNotFoundException;
2930
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.model.AppendOverwriteWorkflowContext;
3031
import org.breedinginsight.brapps.importer.services.processors.experiment.appendoverwrite.model.AppendOverwriteMiddlewareContext;
3132
import org.breedinginsight.brapps.importer.services.processors.experiment.model.ImportContext;
@@ -94,12 +95,12 @@ public List<BrAPIObservationUnit> brapiPost(List<BrAPIObservationUnit> members)
9495
* @throws ApiException if there is an issue with the API call
9596
*/
9697
@Override
97-
public List<BrAPIObservationUnit> brapiRead() throws ApiException {
98+
public List<BrAPIObservationUnit> brapiRead() throws ApiException, EntityNotFoundException {
9899
// Collect deltabreed-generated obs unit ids listed in the import
99100
Set<String> obsUnitIds = cache.getReferenceOUIds();
100101

101102
// For each id fetch the observation unit from the brapi data store
102-
return observationUnitService.getObservationUnitsByDbId(new HashSet<>(obsUnitIds), importContext.getProgram());
103+
return observationUnitService.getObservationUnitsById(new HashSet<>(obsUnitIds), importContext.getProgram());
103104
}
104105

105106
/**

0 commit comments

Comments
 (0)