diff --git a/doc/user/user/sample-metadata/images/loading.png b/doc/user/user/sample-metadata/images/loading.png new file mode 100644 index 00000000000..dd38ae352b4 Binary files /dev/null and b/doc/user/user/sample-metadata/images/loading.png differ diff --git a/doc/user/user/sample-metadata/images/upload-column-after.png b/doc/user/user/sample-metadata/images/upload-column-after.png new file mode 100644 index 00000000000..938c5f87a8f Binary files /dev/null and b/doc/user/user/sample-metadata/images/upload-column-after.png differ diff --git a/doc/user/user/sample-metadata/images/upload-column-before.png b/doc/user/user/sample-metadata/images/upload-column-before.png new file mode 100644 index 00000000000..2475c27b62e Binary files /dev/null and b/doc/user/user/sample-metadata/images/upload-column-before.png differ diff --git a/doc/user/user/sample-metadata/images/upload-column.png b/doc/user/user/sample-metadata/images/upload-column.png deleted file mode 100644 index 3ca4ff8b8fb..00000000000 Binary files a/doc/user/user/sample-metadata/images/upload-column.png and /dev/null differ diff --git a/doc/user/user/sample-metadata/images/upload-preview-errors.png b/doc/user/user/sample-metadata/images/upload-preview-errors.png index dd359cce0b0..9b2a2f748ad 100644 Binary files a/doc/user/user/sample-metadata/images/upload-preview-errors.png and b/doc/user/user/sample-metadata/images/upload-preview-errors.png differ diff --git a/doc/user/user/sample-metadata/images/upload-preview-success.png b/doc/user/user/sample-metadata/images/upload-preview-success.png index 6e2c4f1764e..df4c265657a 100644 Binary files a/doc/user/user/sample-metadata/images/upload-preview-success.png and b/doc/user/user/sample-metadata/images/upload-preview-success.png differ diff --git a/doc/user/user/sample-metadata/images/upload-preview.png b/doc/user/user/sample-metadata/images/upload-preview.png index 469c98cd7be..3b5227de0c8 100644 Binary files a/doc/user/user/sample-metadata/images/upload-preview.png and b/doc/user/user/sample-metadata/images/upload-preview.png differ diff --git a/doc/user/user/sample-metadata/images/upload-selection.png b/doc/user/user/sample-metadata/images/upload-selection.png index e0af432abd0..20bf4ae6db8 100644 Binary files a/doc/user/user/sample-metadata/images/upload-selection.png and b/doc/user/user/sample-metadata/images/upload-selection.png differ diff --git a/doc/user/user/sample-metadata/index.md b/doc/user/user/sample-metadata/index.md index 963729049cd..eba365b2c5e 100644 --- a/doc/user/user/sample-metadata/index.md +++ b/doc/user/user/sample-metadata/index.md @@ -37,23 +37,31 @@ Links to the upload page can be found: Any CSV or Excel spreadsheet containing metadata for samples in a project can be uploaded through the IRIDA web interface. One of the column in the table __must__ correspond to the sample name within the project. In this example spreadsheet, the `NLEP #` column is the sample name. -The first step is to select the CSV or Excel file containing the data. Either click on the square label `Click or drop Excel/CSV file containing metadata for samples in this project.` or drag and drop the file from your file browser. +The first step is to select the CSV or Excel file containing the data. Either click or drag the file into the drop zone from your file browser. ![Select spreadsheet](images/upload-selection.png) -After uploading a spreadsheet, the column corresponding to the sample name must be selected. After selecting the column header, press the `Preview the data` button. +After uploading a spreadsheet, you will be brought to the `Map Columns` step. The column corresponding to the sample name must be selected. -![Select name column.](images/upload-column.png) +![Select name column.](images/upload-column-before.png) -Rows that do not match an existing sample name are identified with the `New` tag. If selected, these samples will automatically be created. Rows that do match an existing sample name will be updated. Only select the rows that are to be uploaded and press the `Upload the data` button. +Once the sample name column is selected, a table will be displayed listing all the metadata fields. You can review the existing and target metadata field restrictions here. Press the `Review the data` button to continue. + +![Select name column.](images/upload-column-after.png) + +You may select the rows that are to be uploaded on the `Review Data` step. Rows that do not match an existing sample name are identified with the `New` tag. If selected, these samples will automatically be created. Rows that match an existing sample name will be updated. ![Preview Upload](images/upload-preview.png) -Rows that have an invalid sample name will be highlighted in red. These errors should be fixed within the spreadsheet and re-imported. +Rows that have an invalid sample name will be highlighted in red. These errors should be fixed within the spreadsheet and re-imported. Press the `Upload the data` button to continue. ![Upload Preview Errors](images/upload-preview-errors.png) -The complete page will be displayed on a successful upload. Clicking on the `Upload another file` button will redirect to the beginning of a new upload. +Progress will be displayed while uploading. Please be patient while uploading large data sets. Do not close the window or leave the page. + +![Upload Preview Errors](images/loading.png) + +On a successful upload, you will be brought to the `Complete` step. Clicking on the `Upload another file` button will redirect to the beginning of a new upload. ![Upload Preview Success](images/upload-preview-success.png) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/model/sample/MetadataTemplateField.java b/src/main/java/ca/corefacility/bioinformatics/irida/model/sample/MetadataTemplateField.java index 1cbb08dc44b..4864b69bf5c 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/model/sample/MetadataTemplateField.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/model/sample/MetadataTemplateField.java @@ -1,19 +1,22 @@ package ca.corefacility.bioinformatics.irida.model.sample; -import ca.corefacility.bioinformatics.irida.model.sample.metadata.MetadataEntry; -import org.hibernate.envers.Audited; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.util.List; +import java.util.Objects; import javax.persistence.*; import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Objects; + +import org.hibernate.envers.Audited; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import ca.corefacility.bioinformatics.irida.model.sample.metadata.MetadataEntry; /** * Describes an individual field in a {@link MetadataTemplate}. */ @Entity -@Table(name = "metadata_field") +@Table(name = "metadata_field", + uniqueConstraints = @UniqueConstraint(columnNames = { "label" }, name = "UK_METADATA_FIELD_LABEL")) @Audited @EntityListeners(AuditingEntityListener.class) public class MetadataTemplateField { diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorage.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorage.java deleted file mode 100644 index d3b1f8c8566..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.utilities; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Used to store information relating to sample metadata during upload. - */ -public class SampleMetadataStorage { - private String sampleNameColumn; - private List headers; - private List rows; - - public void setSampleNameColumn(String sampleColumnName) { - this.sampleNameColumn = sampleColumnName; - } - - public void setHeaders(List headers) { - this.headers = headers; - } - - public String getSampleNameColumn() { - return sampleNameColumn; - } - - public List getHeaders() { - return headers; - } - - public List getRows() { - return rows; - } - - /** - * Returns the row from storage given the sample name and column name - * - * @param sampleName the name of the sample - * @param sampleNameColumn the header name of the sample column - * @return the value associated with the key - */ - public SampleMetadataStorageRow getRow(String sampleName, String sampleNameColumn) { - return rows.stream() - .filter(row -> sampleName.equals(row.getEntryValue(sampleNameColumn))) - .findFirst() - .orElse(null); - } - - public List getFoundRows() { - return rows == null ? - Collections.emptyList() : - rows.stream() - .filter((r) -> r != null && r.getFoundSampleId() != null) - .collect(Collectors.toList()); - } - - public void setRows(List rows) { - this.rows = rows; - } - - /** - * remove all rows - */ - public void removeRows() { - this.rows = null; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - SampleMetadataStorage that = (SampleMetadataStorage) o; - return Objects.equals(sampleNameColumn, that.sampleNameColumn) && Objects.equals(headers, that.headers) - && Objects.equals(rows, that.rows); - } - -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorageRow.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorageRow.java deleted file mode 100644 index 867ac1243b1..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/utilities/SampleMetadataStorageRow.java +++ /dev/null @@ -1,71 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.utilities; - -import java.util.Map; -import java.util.Objects; - -/** - * Used to store information relating to sample metadata rows during upload. - */ -public class SampleMetadataStorageRow { - - private Map entry; - private Long foundSampleId; - private String error; - private Boolean isSaved; - - public SampleMetadataStorageRow(Map entry) { - this.entry = entry; - } - - public Map getEntry() { - return entry; - } - - /** - * Returns the associated value to which the given key is mapped - * - * @param key of the map - * @return the value associated with the key - */ - public String getEntryValue(String key) { - return entry.get(key); - } - - public void setEntry(Map entry) { - this.entry = entry; - } - - public Long getFoundSampleId() { - return foundSampleId; - } - - public void setFoundSampleId(Long foundSampleId) { - this.foundSampleId = foundSampleId; - } - - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - public Boolean isSaved() { - return isSaved; - } - - public void setSaved(Boolean saved) { - isSaved = saved; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - SampleMetadataStorageRow that = (SampleMetadataStorageRow) o; - return Objects.equals(entry, that.entry) && Objects.equals(foundSampleId, that.foundSampleId); - } -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/CreateSampleResponse.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/CreateSampleResponse.java new file mode 100644 index 00000000000..9f5cfc02303 --- /dev/null +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/CreateSampleResponse.java @@ -0,0 +1,25 @@ +package ca.corefacility.bioinformatics.irida.ria.web.ajax.dto; + +/** + * UI response to create a new sample + */ +public class CreateSampleResponse extends UpdateSampleResponse { + private Long sampleId; + + public CreateSampleResponse(String errorMessage) { + super(true, errorMessage); + } + + public CreateSampleResponse(Long sampleId) { + super(false); + this.sampleId = sampleId; + } + + public Long getSampleId() { + return sampleId; + } + + public void setSampleId(Long sampleId) { + this.sampleId = sampleId; + } +} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleRequest.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleRequest.java index e7964d2b15a..c193fd7cf10 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleRequest.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleRequest.java @@ -8,7 +8,19 @@ * UI Request to update an existing sample */ public class UpdateSampleRequest extends CreateSampleRequest { - public UpdateSampleRequest(String name, String organism, String description, List metadata) { + private Long sampleId; + + public UpdateSampleRequest(Long sampleID, String name, String organism, String description, + List metadata) { super(name, organism, description, metadata); + this.sampleId = sampleID; + } + + public Long getSampleId() { + return sampleId; + } + + public void setSampleId(Long sampleId) { + this.sampleId = sampleId; } } diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleResponse.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleResponse.java new file mode 100644 index 00000000000..a98fb5b9878 --- /dev/null +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/UpdateSampleResponse.java @@ -0,0 +1,37 @@ +package ca.corefacility.bioinformatics.irida.ria.web.ajax.dto; + +/** + * UI response to update an existing sample + */ +public class UpdateSampleResponse { + private boolean error; + private String errorMessage; + + public UpdateSampleResponse() { + } + + public UpdateSampleResponse(boolean error) { + this.error = error; + } + + public UpdateSampleResponse(boolean error, String errorMessage) { + this.error = error; + this.errorMessage = errorMessage; + } + + public boolean isError() { + return error; + } + + public void setError(boolean error) { + this.error = error; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/ajax/AjaxMultipleResponse.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/ajax/AjaxMultipleResponse.java new file mode 100644 index 00000000000..71d8276226e --- /dev/null +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/dto/ajax/AjaxMultipleResponse.java @@ -0,0 +1,19 @@ +package ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax; + +import java.util.Map; + +/** + * AJAX response to return multiple responses to the client + */ +public class AjaxMultipleResponse extends AjaxResponse { + private Map responses; + + public AjaxMultipleResponse(Map responses) { + this.responses = responses; + } + + public Map getResponses() { + return responses; + } +} + diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/metadata/MetadataAjaxController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/metadata/MetadataAjaxController.java index a1a0d0c5098..f43d9709fc9 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/metadata/MetadataAjaxController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/metadata/MetadataAjaxController.java @@ -115,8 +115,12 @@ public List getMetadataFieldsForProject(@RequestParam Long @PostMapping("/fields") public ResponseEntity createMetadataFieldsForProject(@RequestParam Long projectId, @RequestBody List fields, Locale locale) { - return ResponseEntity.ok( - new AjaxSuccessResponse(service.createMetadataFieldsForProject(projectId, fields, locale))); + try { + return ResponseEntity.ok( + new AjaxSuccessResponse(service.createMetadataFieldsForProject(projectId, fields, locale))); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(new AjaxErrorResponse(e.getMessage())); + } } /** diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/projects/ProjectSamplesAjaxController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/projects/ProjectSamplesAjaxController.java index 1004762ab75..15346c68273 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/projects/ProjectSamplesAjaxController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/ajax/projects/ProjectSamplesAjaxController.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -15,6 +16,7 @@ import ca.corefacility.bioinformatics.irida.model.sample.Sample; import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.*; import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxErrorResponse; +import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxMultipleResponse; import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxSuccessResponse; import ca.corefacility.bioinformatics.irida.ria.web.ajax.projects.dto.ValidateSampleNamesRequest; @@ -62,31 +64,55 @@ public ResponseEntity validateNewSampleName(@Reque } /** - * Create a new sample within a project + * Create new samples within a project * - * @param request Details about the sample + * @param requests Details about the samples * @param projectId current project identifier - * @param locale current users locale - * @return result of creating the project + * @return result of creating the samples */ - @PostMapping("/add-sample") - public ResponseEntity createSampleInProject(@RequestBody CreateSampleRequest request, - @PathVariable long projectId, Locale locale) { - return uiProjectSampleService.createSample(request, projectId, locale); + @PostMapping("/create") + public ResponseEntity createSamplesInProject(@RequestBody CreateSampleRequest[] requests, + @PathVariable long projectId) { + Map responses = uiProjectSampleService.createSamples(requests, projectId); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + long successCount = responses.entrySet() + .stream() + .filter(response -> !((CreateSampleResponse) response.getValue()).isError()) + .count(); + if (responses.size() == successCount) { + return ResponseEntity.status(HttpStatus.OK).body(new AjaxMultipleResponse(responses)); + } else if (responses.size() == errorCount) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(new AjaxMultipleResponse(responses)); + } + return ResponseEntity.status(HttpStatus.MULTI_STATUS).body(new AjaxMultipleResponse(responses)); } /** - * Update a sample within a project + * Update samples within a project * - * @param request Details about the sample - * @param sampleId sample identifier - * @param locale current users locale - * @return result of creating the project + * @param requests Details about the samples + * @return result of updating the samples */ - @PatchMapping("/add-sample/{sampleId}") - public ResponseEntity updateSampleInProject(@RequestBody UpdateSampleRequest request, - @PathVariable Long projectId, @PathVariable long sampleId, Locale locale) { - return uiProjectSampleService.updateSample(request, sampleId, locale); + @PatchMapping("/update") + public ResponseEntity updateSamplesInProject(@RequestBody UpdateSampleRequest[] requests) { + Map responses = uiProjectSampleService.updateSamples(requests); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((UpdateSampleResponse) response.getValue()).isError()) + .count(); + long successCount = responses.entrySet() + .stream() + .filter(response -> !((UpdateSampleResponse) response.getValue()).isError()) + .count(); + if (responses.size() == successCount) { + return ResponseEntity.status(HttpStatus.OK).body(new AjaxMultipleResponse(responses)); + } else if (responses.size() == errorCount) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(new AjaxMultipleResponse(responses)); + } + return ResponseEntity.status(HttpStatus.MULTI_STATUS).body(new AjaxMultipleResponse(responses)); } /** diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/SavedMetadataException.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/SavedMetadataException.java deleted file mode 100644 index 373b3b195ad..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/SavedMetadataException.java +++ /dev/null @@ -1,19 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.web.errors; - -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; - -/** - * Returns the SampleMetadataStorage on error. - */ -public class SavedMetadataException extends Exception { - private SampleMetadataStorage storage; - - public SavedMetadataException(SampleMetadataStorage storage) { - super(); - this.storage = storage; - } - - public SampleMetadataStorage getStorage() { - return storage; - } -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/dto/SavedMetadataErrorResponse.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/dto/SavedMetadataErrorResponse.java deleted file mode 100644 index c291440f4ca..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/dto/SavedMetadataErrorResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.web.projects.dto; - -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; - -/** - * Returns the SampleMetadataStorage on error. - */ -public class SavedMetadataErrorResponse extends AjaxResponse { - private SampleMetadataStorage storage; - - public SavedMetadataErrorResponse(SampleMetadataStorage storage) { - this.storage = storage; - } - - public SampleMetadataStorage getStorage() { - return storage; - } -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/metadata/ProjectSampleMetadataAjaxController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/metadata/ProjectSampleMetadataAjaxController.java deleted file mode 100644 index 1a9a655a0b9..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/projects/metadata/ProjectSampleMetadataAjaxController.java +++ /dev/null @@ -1,138 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.web.projects.metadata; - -import java.util.*; - -import javax.servlet.http.HttpSession; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import ca.corefacility.bioinformatics.irida.model.project.Project; -import ca.corefacility.bioinformatics.irida.model.sample.Sample; -import ca.corefacility.bioinformatics.irida.model.sample.metadata.MetadataEntry; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxSuccessResponse; -import ca.corefacility.bioinformatics.irida.ria.web.errors.SavedMetadataException; -import ca.corefacility.bioinformatics.irida.ria.web.projects.dto.SavedMetadataErrorResponse; -import ca.corefacility.bioinformatics.irida.ria.web.services.UIMetadataImportService;; - -/** - * This class is designed to be used for bulk actions on {@link MetadataEntry} - * within a {@link Project}. - */ -@Controller -@RequestMapping("/ajax/projects/sample-metadata/upload") -public class ProjectSampleMetadataAjaxController { - private final UIMetadataImportService metadataImportService; - - @Autowired - public ProjectSampleMetadataAjaxController(UIMetadataImportService metadataImportService) { - this.metadataImportService = metadataImportService; - } - - /** - * Upload CSV or Excel file containing sample metadata and extract the - * headers. The file is stored in the session until the column that - * corresponds to a {@link Sample} identifier has been sent. - * - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current {@link Project} - * @param file - * {@link MultipartFile} The csv or excel file containing the - * metadata. - * @return {@link SampleMetadataStorage} which includes a {@link List} of - * headers and rows from the csv or excel file. - * @throws Exception - * if there is an error reading the file - */ - @PostMapping("/file") - @ResponseBody - public ResponseEntity createProjectSampleMetadata(HttpSession session, - @RequestParam Long projectId, @RequestParam("file") MultipartFile file) throws Exception { - return ResponseEntity.ok(metadataImportService.createProjectSampleMetadata(session, projectId, file)); - } - - /** - * Add the metadata to specific {@link Sample} based on the selected column - * to correspond to the {@link Sample} id. - * - * @param session - * {@link HttpSession}. - * @param projectId - * {@link Long} identifier for the current {@link Project}. - * @param sampleNameColumn - * {@link String} the header to used to represent the - * {@link Sample} identifier. - * @return a complete message. - */ - @PutMapping("/setSampleColumn") - @ResponseBody - public ResponseEntity setProjectSampleMetadataSampleId(HttpSession session, - @RequestParam Long projectId, @RequestParam String sampleNameColumn) { - return ResponseEntity.ok(new AjaxSuccessResponse( - metadataImportService.setProjectSampleMetadataSampleId(session, projectId, sampleNameColumn))); - } - - /** - * Save uploaded metadata from the session into IRIDA. - * - * @param locale - * {@link Locale} of the current user. - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current project - * @param sampleNames - * {@link List} of {@link String} sample names - * @return {@link String} message of how many samples were created and/or - * updated. - */ - @PostMapping("/save") - @ResponseBody - public ResponseEntity saveProjectSampleMetadata(Locale locale, HttpSession session, - @RequestParam Long projectId, @RequestParam List sampleNames) { - try { - return ResponseEntity.ok(new AjaxSuccessResponse( - metadataImportService.saveProjectSampleMetadata(locale, session, projectId, sampleNames))); - } catch (SavedMetadataException e) { - return ResponseEntity.status(HttpStatus.CONFLICT).body(new SavedMetadataErrorResponse(e.getStorage())); - } - } - - /** - * Clear any uploaded sample metadata stored into the session. - * - * @param session - * {@link HttpSession} - * @param projectId - * identifier for the {@link Project} currently uploaded metadata - * to. - */ - @DeleteMapping("/clear") - public void clearProjectSampleMetadata(HttpSession session, @RequestParam Long projectId) { - metadataImportService.clearProjectSampleMetadata(session, projectId); - } - - /** - * Get the currently stored metadata. - * - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current {@link Project} - * @return the currently stored {@link SampleMetadataStorage} - */ - @GetMapping("/getMetadata") - @ResponseBody - public ResponseEntity getProjectSampleMetadata(HttpSession session, - @RequestParam Long projectId) { - return ResponseEntity.ok(metadataImportService.getProjectSampleMetadata(session, projectId)); - } -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataFileImportService.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataFileImportService.java deleted file mode 100644 index c79b32134c8..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataFileImportService.java +++ /dev/null @@ -1,255 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.web.services; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; -import ca.corefacility.bioinformatics.irida.exceptions.MetadataImportFileTypeNotSupportedError; -import ca.corefacility.bioinformatics.irida.model.project.Project; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorageRow; -import ca.corefacility.bioinformatics.irida.service.ProjectService; -import ca.corefacility.bioinformatics.irida.service.sample.SampleService; - -import com.google.common.base.Strings; - -/** - * UI service to handle parsing metadata files so they can be saved to the - * session. - */ -@Component -public class UIMetadataFileImportService { - - private static final Logger logger = LoggerFactory.getLogger(UIMetadataFileImportService.class); - - private final ProjectService projectService; - private final SampleService sampleService; - - @Autowired - public UIMetadataFileImportService(ProjectService projectService, SampleService sampleService) { - this.projectService = projectService; - this.sampleService = sampleService; - } - - /** - * Parse metadata from an csv file. - * - * @param projectId - * {@link Long} The project identifier. - * @param inputStream - * The inputStream of the csv file. - * @return {@link SampleMetadataStorage} contains the metadata from file. - * @throws IOException - * thrown if the extension does not exist. - */ - public SampleMetadataStorage parseCSV(Long projectId, InputStream inputStream) throws IOException { - SampleMetadataStorage storage = new SampleMetadataStorage(); - - CSVParser parser = CSVParser.parse(inputStream, StandardCharsets.UTF_8, - CSVFormat.RFC4180.withFirstRecordAsHeader().withTrim().withIgnoreEmptyLines()); - List rows = new ArrayList<>(); - - // save headers - Map headersSet = parser.getHeaderMap(); - List headersList = new ArrayList<>(headersSet.keySet()); - storage.setHeaders(headersList); - - // save data - for (CSVRecord row : parser) { - Map rowMap = new HashMap<>(); - for (String key : row.toMap().keySet()) { - String value = row.toMap().get(key); - rowMap.put(key, value); - } - rows.add(new SampleMetadataStorageRow(rowMap)); - } - storage.setRows(rows); - storage.setSampleNameColumn(findColumnName(projectId, rows)); - parser.close(); - - return storage; - } - - /** - * Parse metadata from an excel file. - * - * @param projectId - * {@link Long} The project identifier. - * @param inputStream - * The inputStream of the excel file. - * @param extension - * The extension of the excel file. - * @return {@link SampleMetadataStorage} contains the metadata from file. - * @throws IOException - * thrown if the extension does not exist. - */ - public SampleMetadataStorage parseExcel(Long projectId, InputStream inputStream, String extension) - throws IOException { - SampleMetadataStorage storage = new SampleMetadataStorage(); - Workbook workbook = null; - - // Check the type of workbook - switch (extension) { - case "xlsx": - workbook = new XSSFWorkbook(inputStream); - break; - case "xls": - workbook = new HSSFWorkbook(inputStream); - break; - default: - // Should never reach here as the uploader limits to .csv, .xlsx and - // .xlx files. - throw new MetadataImportFileTypeNotSupportedError(extension); - } - - // Only look at the first sheet in the workbook as this should be the - // file we want. - Sheet sheet = workbook.getSheetAt(0); - Iterator rowIterator = sheet.iterator(); - - List headers = getWorkbookHeaders(rowIterator.next()); - storage.setHeaders(headers); - - // Get the metadata out of the table. - List rows = new ArrayList<>(); - while (rowIterator.hasNext()) { - Map rowMap = new HashMap<>(); - Row row = rowIterator.next(); - Iterator cellIterator = row.cellIterator(); - while (cellIterator.hasNext()) { - Cell cell = cellIterator.next(); - - int columnIndex = cell.getColumnIndex(); - if (columnIndex < headers.size()) { - String header = headers.get(columnIndex); - - if (!Strings.isNullOrEmpty(header)) { - // Need to ignore empty headers. - if (cell.getCellType().equals(CellType.NUMERIC)) { - /* - * This is a special handler for number cells. It - * was requested that numbers keep their formatting - * from their excel files. E.g. 2.222222 with - * formatting for 2 decimal places will be saved as - * 2.22. - */ - DataFormatter formatter = new DataFormatter(); - String value = formatter.formatCellValue(cell); - rowMap.put(header, value); - } else { - rowMap.put(header, cell.getStringCellValue()); - } - } - } - } - rows.add(new SampleMetadataStorageRow(rowMap)); - } - storage.setRows(rows); - storage.setSampleNameColumn(findColumnName(projectId, rows)); - - if (extension.equals("xlsx")) { - workbook.close(); - } - - return storage; - } - - /** - * Extract the headers from an excel file. - * - * @param row - * {@link Row} First row from the excel file. - * @return {@link List} of {@link String} header values. - */ - private List getWorkbookHeaders(Row row) { - // We want to return a list of the table headers back to the UI. - List headers = new ArrayList<>(); - - // Get the column headers - Iterator headerIterator = row.cellIterator(); - while (headerIterator.hasNext()) { - Cell headerCell = headerIterator.next(); - CellType cellType = headerCell.getCellType(); - - String headerValue; - if (cellType.equals(CellType.STRING)) { - headerValue = headerCell.getStringCellValue().trim(); - } else { - headerValue = String.valueOf(headerCell.getNumericCellValue()).trim(); - } - - // Leave empty headers for now, we will remove those columns later. - headers.add(headerValue); - } - return headers; - } - - /** - * Find the sample name column, given the rows of a file. - * - * @param projectId - * {@link Long} The project identifier. - * @param rows - * {@link Row} The rows from the excel file. - * @return {@link String} column name. - */ - private String findColumnName(Long projectId, List rows) { - String columnName = null; - int col = 0; - int numRows = rows.size(); - - while (columnName == null && col < numRows) { - columnName = findColumnNameInRow(projectId, rows.get(col)); - col++; - } - - return columnName; - } - - /** - * Find the sample name column, given a row of a file. - * - * @param projectId - * {@link Long} The project identifier. - * @param row - * {@link Row} A row from the excel file. - * @return {@link String} column name. - */ - private String findColumnNameInRow(Long projectId, SampleMetadataStorageRow row) { - String columnName = null; - Project project = projectService.read(projectId); - Iterator> iterator = row.getEntry().entrySet().iterator(); - - while (iterator.hasNext() && columnName == null) { - String key = null; - String value = null; - try { - Map.Entry entry = iterator.next(); - key = entry.getKey(); - value = entry.getValue(); - - if (sampleService.getSampleBySampleName(project, value) != null) { - columnName = key; - } - } catch (EntityNotFoundException entityNotFoundException) { - logger.trace("Sample " + value + " in project " + project.getId() + " is not found.", - entityNotFoundException); - } - } - - return columnName; - } -} \ No newline at end of file diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataImportService.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataImportService.java deleted file mode 100644 index 6e7520fe6ce..00000000000 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIMetadataImportService.java +++ /dev/null @@ -1,274 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.web.services; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -import javax.servlet.http.HttpSession; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.MessageSource; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; -import ca.corefacility.bioinformatics.irida.exceptions.MetadataImportFileTypeNotSupportedError; -import ca.corefacility.bioinformatics.irida.model.project.Project; -import ca.corefacility.bioinformatics.irida.model.sample.MetadataTemplateField; -import ca.corefacility.bioinformatics.irida.model.sample.Sample; -import ca.corefacility.bioinformatics.irida.model.sample.metadata.MetadataEntry; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorageRow; -import ca.corefacility.bioinformatics.irida.ria.web.errors.SavedMetadataException; -import ca.corefacility.bioinformatics.irida.service.ProjectService; -import ca.corefacility.bioinformatics.irida.service.sample.MetadataTemplateService; -import ca.corefacility.bioinformatics.irida.service.sample.SampleService; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; - -/** - * UI service to handle importing metadata files, so they can be saved to the - * session. - */ -@Component -public class UIMetadataImportService { - - private static final Logger logger = LoggerFactory.getLogger(UIMetadataImportService.class); - private final MessageSource messageSource; - private final ProjectService projectService; - private final SampleService sampleService; - private final MetadataTemplateService metadataTemplateService; - private final UIMetadataFileImportService metadataFileImportService; - - @Autowired - public UIMetadataImportService(MessageSource messageSource, ProjectService projectService, - SampleService sampleService, MetadataTemplateService metadataTemplateService, - UIMetadataFileImportService metadataFileImportService) { - this.messageSource = messageSource; - this.projectService = projectService; - this.sampleService = sampleService; - this.metadataTemplateService = metadataTemplateService; - this.metadataFileImportService = metadataFileImportService; - } - - /** - * Upload CSV or Excel file containing sample metadata and extract the - * headers. The file is stored in the session until the column that - * corresponds to a {@link Sample} identifier has been sent. - * - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current {@link Project} - * @param file - * {@link MultipartFile} The csv or excel file containing the - * metadata. - * @return {@link Map} of headers and rows from the csv or excel file for - * the user to select the header corresponding the {@link Sample} - * identifier. - * @throws Exception - * if there is an error reading the file - */ - public SampleMetadataStorage createProjectSampleMetadata(HttpSession session, Long projectId, MultipartFile file) - throws Exception { - // We want to return a list of the table headers back to the UI. - SampleMetadataStorage storage = new SampleMetadataStorage(); - try (InputStream inputStream = file.getInputStream()) { - String filename = file.getOriginalFilename(); - String extension = Files.getFileExtension(filename); - - // Check the file type - switch (extension) { - case "csv": - storage = metadataFileImportService.parseCSV(projectId, inputStream); - break; - case "xlsx": - case "xls": - storage = metadataFileImportService.parseExcel(projectId, inputStream, extension); - break; - default: - // Should never reach here as the uploader limits to .csv, .xlsx - // and .xlx files. - throw new MetadataImportFileTypeNotSupportedError(extension); - } - - } catch (FileNotFoundException e) { - logger.debug("No file found for uploading an excel file of metadata."); - throw e; - } catch (IOException e) { - logger.error("Error opening file" + file.getOriginalFilename()); - throw e; - } - - session.setAttribute("pm-" + projectId, storage); - return storage; - } - - /** - * Add the metadata to specific {@link Sample} based on the selected column - * to correspond to the {@link Sample} id. - * - * @param session - * {@link HttpSession}. - * @param projectId - * {@link Long} identifier for the current {@link Project}. - * @param sampleNameColumn - * {@link String} the header to used to represent the - * {@link Sample} identifier. - * @return {@link String} containing a complete message. - */ - public String setProjectSampleMetadataSampleId(HttpSession session, Long projectId, String sampleNameColumn) { - // Attempt to get the metadata from the sessions - SampleMetadataStorage stored = (SampleMetadataStorage) session.getAttribute("pm-" + projectId); - - if (stored != null) { - stored.setSampleNameColumn(sampleNameColumn); - Project project = projectService.read(projectId); - List rows = stored.getRows(); - List updatedRows = new ArrayList<>(); - - // Get the metadata out of the table. - for (SampleMetadataStorageRow row : rows) { - try { - // If this throws an error than the sample does not exist. - Sample sample = sampleService.getSampleBySampleName(project, row.getEntryValue(sampleNameColumn)); - row.setFoundSampleId(sample.getId()); - } catch (EntityNotFoundException e) { - row.setFoundSampleId(null); - } - updatedRows.add(row); - } - stored.setRows(updatedRows); - } - - return "complete"; - } - - /** - * Save uploaded metadata - * - * @param locale - * {@link Locale} of the current user. - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current project - * @param sampleNames - * {@link List} of {@link String} sample names - * @return {@link String} that returns a message and potential errors. - * @throws SavedMetadataException - * if there is an error saving the metadata - */ - public String saveProjectSampleMetadata(Locale locale, HttpSession session, Long projectId, - List sampleNames) throws SavedMetadataException { - List DEFAULT_HEADERS = ImmutableList.of( - messageSource.getMessage("project.samples.table.sample-id", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.id", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.modified-date", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.modified", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.created-date", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.created", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.coverage", new Object[] {}, locale), - messageSource.getMessage("project.samples.table.project-id", new Object[] {}, locale)); - Project project = projectService.read(projectId); - SampleMetadataStorage stored = (SampleMetadataStorage) session.getAttribute("pm-" + projectId); - boolean hasErrors = false; - String message; - int samplesUpdatedCount = 0; - int samplesCreatedCount = 0; - - if (sampleNames != null) { - String sampleNameColumn = stored.getSampleNameColumn(); - - for (String sampleName : sampleNames) { - try { - Set metadataEntrySet = new HashSet<>(); - SampleMetadataStorageRow row = stored.getRow(sampleName, sampleNameColumn); - String name = row.getEntryValue(sampleNameColumn); - Sample sample = null; - - if (row.getFoundSampleId() != null) { - sample = sampleService.getSampleBySampleName(project, name); - samplesUpdatedCount++; - } else { - sample = new Sample(name); - projectService.addSampleToProject(project, sample, true); - samplesCreatedCount++; - } - - // Need to overwrite duplicate keys - for (Map.Entry entry : row.getEntry().entrySet()) { - // Make sure we are not saving non-metadata items. - if (!DEFAULT_HEADERS.contains(entry.getKey()) && !sampleNameColumn.contains(entry.getKey())) { - MetadataTemplateField key = metadataTemplateService - .readMetadataFieldByLabel(entry.getKey()); - - if (key == null) { - key = metadataTemplateService - .saveMetadataField(new MetadataTemplateField(entry.getKey(), "text")); - } - - metadataEntrySet.add(new MetadataEntry(entry.getValue(), "text", key)); - } - } - - // Save metadata back to the sample - sampleService.mergeSampleMetadata(sample, metadataEntrySet); - row.setSaved(true); - } catch (Exception e) { - SampleMetadataStorageRow row = stored.getRow(sampleName, sampleNameColumn); - row.setError(e.getMessage()); - row.setSaved(false); - hasErrors = true; - } - } - } - - if (hasErrors) { - throw new SavedMetadataException(stored); - } - - message = ((samplesUpdatedCount == 1) - ? messageSource.getMessage("server.metadataimport.results.save.success.single-updated", - new Object[] { samplesUpdatedCount }, locale) - : messageSource.getMessage("server.metadataimport.results.save.success.multiple-updated", - new Object[] { samplesUpdatedCount }, locale)); - message += (samplesCreatedCount == 1) - ? messageSource.getMessage("server.metadataimport.results.save.success.single-created", - new Object[] { samplesCreatedCount }, locale) - : messageSource.getMessage("server.metadataimport.results.save.success.multiple-created", - new Object[] { samplesCreatedCount }, locale); - - return message; - } - - /** - * Clear any uploaded sample metadata stored into the session. - * - * @param session - * {@link HttpSession} - * @param projectId - * identifier for the {@link Project} currently uploaded metadata - * to. - */ - public void clearProjectSampleMetadata(HttpSession session, Long projectId) { - session.removeAttribute("pm-" + projectId); - } - - /** - * Get the currently stored metadata. - * - * @param session - * {@link HttpSession} - * @param projectId - * {@link Long} identifier for the current {@link Project} - * @return the currently stored {@link SampleMetadataStorage} - */ - public SampleMetadataStorage getProjectSampleMetadata(HttpSession session, Long projectId) { - return (SampleMetadataStorage) session.getAttribute("pm-" + projectId); - } -} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIProjectSampleService.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIProjectSampleService.java index cfe6e66c4d0..612852bc1f6 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIProjectSampleService.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/services/UIProjectSampleService.java @@ -3,6 +3,8 @@ import java.util.*; import java.util.stream.Collectors; +import javax.validation.ConstraintViolationException; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; @@ -16,14 +18,7 @@ import ca.corefacility.bioinformatics.irida.model.sample.MetadataTemplateField; import ca.corefacility.bioinformatics.irida.model.sample.Sample; import ca.corefacility.bioinformatics.irida.model.sample.metadata.MetadataEntry; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.CreateSampleRequest; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.LockedSamplesResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.SampleNameValidationResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.UpdateSampleRequest; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxCreateItemSuccessResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxErrorResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxUpdateItemSuccessResponse; +import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.*; import ca.corefacility.bioinformatics.irida.ria.web.ajax.projects.dto.MetadataEntryModel; import ca.corefacility.bioinformatics.irida.ria.web.ajax.projects.dto.ValidateSampleNameModel; import ca.corefacility.bioinformatics.irida.ria.web.ajax.projects.dto.ValidateSampleNamesRequest; @@ -122,66 +117,101 @@ public ResponseEntity validateNewSampleName(String } + /** + * Create new samples in a project + * + * @param requests Each {@link CreateSampleRequest} contains details about the sample to create + * @param projectId Identifier for the current project + * @return result of creating the sample + */ + public Map createSamples(CreateSampleRequest[] requests, Long projectId) { + Map responses = new HashMap<>(); + for (CreateSampleRequest request : requests) { + try { + Long sampleId = createSample(projectId, request); + CreateSampleResponse response = new CreateSampleResponse(sampleId); + responses.put(request.getName(), response); + } catch (Exception e) { + CreateSampleResponse response = new CreateSampleResponse(e.getMessage()); + responses.put(request.getName(), response); + } + } + return responses; + } + /** * Create a new sample in a project * - * @param request {@link CreateSampleRequest} details about the sample to create + * @param request {@link CreateSampleRequest} contains details about the sample to create * @param projectId Identifier for the current project - * @param locale Users current locale * @return result of creating the sample + * @throws EntityNotFoundException if the identifier does not exist in the database */ @Transactional - public ResponseEntity createSample(CreateSampleRequest request, Long projectId, Locale locale) { - try { - Project project = projectService.read(projectId); - Sample sample = new Sample(request.getName()); - if (!Strings.isNullOrEmpty(request.getOrganism())) { - sample.setOrganism(request.getOrganism()); - } - if (!Strings.isNullOrEmpty(request.getDescription())) { - sample.setDescription(request.getDescription()); - } - Join join = projectService.addSampleToProjectWithoutEvent(project, sample, true); - if (request.getMetadata() != null) { - Set metadataEntrySet = createMetadata(request.getMetadata()); - sampleService.updateSampleMetadata(sample, metadataEntrySet); + public Long createSample(Long projectId, CreateSampleRequest request) throws EntityNotFoundException { + Project project = projectService.read(projectId); + Sample sample = new Sample(request.getName()); + if (!Strings.isNullOrEmpty(request.getOrganism())) { + sample.setOrganism(request.getOrganism()); + } + if (!Strings.isNullOrEmpty(request.getDescription())) { + sample.setDescription(request.getDescription()); + } + Join join = projectService.addSampleToProjectWithoutEvent(project, sample, true); + if (request.getMetadata() != null) { + Set metadataEntrySet = createMetadata(request.getMetadata()); + sampleService.mergeSampleMetadata(sample, metadataEntrySet); + } + return join.getObject().getId(); + } + + /** + * Update samples in a project + * + * @param requests Each {@link UpdateSampleRequest} contains details about the sample to update + * @return result of creating the samples + */ + public Map updateSamples(UpdateSampleRequest[] requests) { + Map responses = new HashMap<>(); + for (UpdateSampleRequest request : requests) { + try { + updateSample(request); + UpdateSampleResponse response = new UpdateSampleResponse(false); + responses.put(request.getName(), response); + } catch (Exception e) { + UpdateSampleResponse response = new UpdateSampleResponse(true, e.getMessage()); + responses.put(request.getName(), response); } - return ResponseEntity.ok(new AjaxCreateItemSuccessResponse(join.getObject().getId())); - } catch (EntityNotFoundException e) { - return ResponseEntity.ok(new AjaxErrorResponse( - messageSource.getMessage("server.AddSample.error.exists", new Object[] {}, locale))); } + return responses; } /** * Update a sample in a project * - * @param request {@link UpdateSampleRequest} details about the sample to update - * @param sampleId Identifier for the sample - * @param locale Users current locale + * @param request {@link UpdateSampleRequest} contains details about the sample to update * @return result of creating the sample + * @throws EntityNotFoundException if the identifier does not exist in the database + * @throws ConstraintViolationException if the entity being updated contains constraint violations */ @Transactional - public ResponseEntity updateSample(UpdateSampleRequest request, Long sampleId, Locale locale) { - try { - Sample sample = sampleService.read(sampleId); - sample.setSampleName(request.getName()); - if (!Strings.isNullOrEmpty(request.getOrganism())) { - sample.setOrganism(request.getOrganism()); - } - if (request.getDescription() != null) { - sample.setDescription(request.getDescription()); - } - if (request.getMetadata() != null) { - Set metadataEntrySet = createMetadata(request.getMetadata()); - sampleService.mergeSampleMetadata(sample, metadataEntrySet); - } - sampleService.update(sample); - return ResponseEntity.ok(new AjaxUpdateItemSuccessResponse( - messageSource.getMessage("server.AddSample.success", null, locale))); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.CONFLICT).body(new AjaxErrorResponse(e.getMessage())); + public Sample updateSample(UpdateSampleRequest request) + throws EntityNotFoundException, ConstraintViolationException { + Long sampleId = request.getSampleId(); + Sample sample = sampleService.read(sampleId); + sample.setSampleName(request.getName()); + if (!Strings.isNullOrEmpty(request.getOrganism())) { + sample.setOrganism(request.getOrganism()); } + if (request.getDescription() != null) { + sample.setDescription(request.getDescription()); + } + if (request.getMetadata() != null) { + Set metadataEntrySet = createMetadata(request.getMetadata()); + sampleService.updateSampleMetadata(sample, metadataEntrySet); + } + return sampleService.update(sample); + } /** diff --git a/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/all-changes.xml b/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/all-changes.xml index 61d30a2d3fa..ea58a9e4165 100644 --- a/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/all-changes.xml +++ b/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/all-changes.xml @@ -1,4 +1,6 @@ \ No newline at end of file + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> + + \ No newline at end of file diff --git a/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/metadata-field-add-label-constraint.xml b/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/metadata-field-add-label-constraint.xml new file mode 100644 index 00000000000..0b2f2be52ba --- /dev/null +++ b/src/main/resources/ca/corefacility/bioinformatics/irida/database/changesets/unreleased/metadata-field-add-label-constraint.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index b0be51f3ceb..4c87a86557d 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -2018,6 +2018,7 @@ SampleMetadataImportReview.alert.valid.rule2=minimum 3 characters long SampleMetadataImportReview.alert.valid.rule3=contain only alphanumeric characters and '-', '_' SampleMetadataImportReview.alert.locked.description.popover.content={0} sample metadata SampleMetadataImportReview.alert.locked.description=\ cannot be imported, because these samples are locked in the current project. +SampleMetadataImportReview.notification.partialError=There are {0} rows that were unable to be saved. Please review the errors in the table. SampleMetadataImportComplete.result.title=The sample metadata imported successfully! SampleMetadataImportComplete.button.upload=Upload another file @@ -2305,7 +2306,6 @@ AddSample.submit=Create Sample server.AddSample.error.length=Sample name must have at least 4 characters. server.AddSample.error.special.characters=Sample name cannot contain any spaces or special characters. server.AddSample.error.exists=A sample by this name already exists in this project. -server.AddSample.success=success # ========================================================================================== # # CART EMPTY COMPONENT # diff --git a/src/main/webapp/resources/js/apis/projects/samples.ts b/src/main/webapp/resources/js/apis/projects/samples.ts index a9a44ac5992..fde366a7c6b 100644 --- a/src/main/webapp/resources/js/apis/projects/samples.ts +++ b/src/main/webapp/resources/js/apis/projects/samples.ts @@ -25,6 +25,15 @@ export interface LockedSamplesResponse { sampleIds: number[]; } +export interface SamplesResponse { + responses: Record; +} + +export interface SampleItemErrorResponse { + error: boolean; + errorMessage: string; +} + export interface MetadataItem { [field: string]: string; rowKey: string; @@ -35,18 +44,37 @@ export interface FieldUpdate { value: string; } -export interface SampleRequest { +export interface UpdateSampleItem extends CreateSampleItem { + sampleId: number; +} + +export interface CreateSampleItem { name: string; organism?: string; description?: string; metadata: FieldUpdate[]; } +export interface UpdateSamplesRequest { + projectId: string; + body: UpdateSampleItem[]; +} + +export interface CreateSamplesRequest { + projectId: string; + body: CreateSampleItem[]; +} + export interface ValidateSampleNamesRequest { samples: ValidateSampleNameModel[]; associatedProjectIds?: number[]; } +export type CreateUpdateSamples = (params: { + projectId: string; + body: Array | Array; +}) => Promise; + const PROJECT_ID = getProjectIdFromUrl(); const URL = setBaseUrl(`/ajax/projects`); @@ -129,30 +157,51 @@ export async function getLockedSamples({ return response.data; } -export async function createSample({ +export const createSamples: CreateUpdateSamples = async ({ projectId, body, -}: { - projectId: string; - body: SampleRequest; -}) { - return await axios.post(`${URL}/${projectId}/samples/add-sample`, body); -} +}) => { + try { + const { data } = await axios.post( + `${URL}/${projectId}/samples/create`, + body + ); + return Promise.resolve(data); + } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response) { + return Promise.resolve(error.response.data); + } else { + return Promise.reject(error.message); + } + } else { + return Promise.reject("An unexpected error occurred"); + } + } +}; -export async function updateSample({ +export const updateSamples: CreateUpdateSamples = async ({ projectId, - sampleId, body, -}: { - projectId: string; - sampleId: number; - body: SampleRequest; -}) { - return await axios.patch( - `${URL}/${projectId}/samples/add-sample/${sampleId}`, - body - ); -} +}) => { + try { + const { data } = await axios.patch( + `${URL}/${projectId}/samples/update`, + body + ); + return Promise.resolve(data); + } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response) { + return Promise.resolve(error.response.data); + } else { + return Promise.reject(error.message); + } + } else { + return Promise.reject("An unexpected error occurred"); + } + } +}; /** * Server side validation of a new sample name. @@ -168,25 +217,6 @@ export async function validateSampleName(name: string) { return response.json(); } -/** - * Create a new sample within a project - * @param name - name of the new sample - * @param organism - name of the organism (optional) - * @returns {Promise} - */ -export async function createNewSample({ - name, - organism, -}: { - name: string; - organism: string; -}) { - return post(`${URL}/${PROJECT_ID}/samples/add-sample`, { - name: name.trim(), - organism, - }); -} - /** * Share or move samples with another project. * @param currentId - current projectId diff --git a/src/main/webapp/resources/js/components/Buttons/AddMemberButton.jsx b/src/main/webapp/resources/js/components/Buttons/AddMemberButton.jsx index 2d888be7e44..2c63657062e 100644 --- a/src/main/webapp/resources/js/components/Buttons/AddMemberButton.jsx +++ b/src/main/webapp/resources/js/components/Buttons/AddMemberButton.jsx @@ -173,7 +173,11 @@ export function AddMemberButton({ }} > {projectRoles.map((role) => ( - + {role.label} ))} diff --git a/src/main/webapp/resources/js/components/files/DragUpload.tsx b/src/main/webapp/resources/js/components/files/DragUpload.tsx index 97d6b498070..b84797c5915 100644 --- a/src/main/webapp/resources/js/components/files/DragUpload.tsx +++ b/src/main/webapp/resources/js/components/files/DragUpload.tsx @@ -1,8 +1,8 @@ import React from "react"; -import type { UploadProps } from "antd"; import { Upload } from "antd"; import { IconFileUpload } from "../icons/Icons"; import { SPACE_SM, SPACE_XS } from "../../styles/spacing"; +import { DraggerProps } from "antd/lib/upload"; const { Dragger } = Upload; @@ -13,8 +13,8 @@ export interface Dictionary { export interface DragUploadProps { uploadText: string | React.ReactElement; uploadHint: string | React.ReactElement; - options: UploadProps; - props: Dictionary; + options: DraggerProps; + props?: Dictionary; } /** * React component for rendering the drag and drop upload functionality. diff --git a/src/main/webapp/resources/js/pages/UserGroupsPage/components/UserGroupMembersTable.jsx b/src/main/webapp/resources/js/pages/UserGroupsPage/components/UserGroupMembersTable.jsx index 5dc9c6f82ff..4d4a85a4e29 100644 --- a/src/main/webapp/resources/js/pages/UserGroupsPage/components/UserGroupMembersTable.jsx +++ b/src/main/webapp/resources/js/pages/UserGroupsPage/components/UserGroupMembersTable.jsx @@ -9,17 +9,11 @@ import { import { RemoveTableItemButton } from "../../../components/Buttons"; import { GroupRole } from "../../../components/roles/GroupRole"; import { SPACE_XS } from "../../../styles/spacing"; -import { - formatInternationalizedDateTime -} from "../../../utilities/date-utilities"; +import { formatInternationalizedDateTime } from "../../../utilities/date-utilities"; import { stringSorter } from "../../../utilities/table-utilities"; import { setBaseUrl } from "../../../utilities/url-utilities"; -import { - AddUserToGroupButton -} from "../../admin/components/user-groups/AddUserToGroupButton"; -import { - getPaginationOptions -} from "../../../utilities/antdesign-table-utilities"; +import { AddUserToGroupButton } from "../../admin/components/user-groups/AddUserToGroupButton"; +import { getPaginationOptions } from "../../../utilities/antdesign-table-utilities"; /** * Custom sorter for the name column since this is NOT paged server side. @@ -56,6 +50,7 @@ export default function UserGroupMembersTable({ ); }, + defaultSortOrder: "ascend", }, { title: i18n("UserGroupMembersTable.role"), diff --git a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportComplete.tsx b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportComplete.tsx index 74501379556..5e8f3c7166d 100644 --- a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportComplete.tsx +++ b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportComplete.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Button, Result } from "antd"; import { SampleMetadataImportWizard } from "./SampleMetadataImportWizard"; @@ -13,22 +13,43 @@ import { NavigateFunction } from "react-router/dist/lib/hooks"; * @constructor */ export function SampleMetadataImportComplete(): JSX.Element { - const { metadata, metadataValidateDetails, metadataSaveDetails } = - useImportSelector((state: ImportState) => state.importReducer); + const { + sampleNameColumn, + metadata, + metadataValidateDetails, + metadataSaveDetails, + } = useImportSelector((state: ImportState) => state.importReducer); - const samplesUpdatedCount = metadata.filter( - (metadataItem: MetadataItem) => - metadataSaveDetails[metadataItem.rowKey]?.saved === true && - !metadataValidateDetails[metadataItem.rowKey].locked && - metadataValidateDetails[metadataItem.rowKey].foundSampleId - ).length; + const filteredSamples = React.useCallback( + (metadataItem: MetadataItem, isSampleFound: boolean) => { + return ( + metadataSaveDetails[metadataItem[sampleNameColumn]]?.saved === true && + !metadataValidateDetails[metadataItem[sampleNameColumn]].locked && + (isSampleFound + ? metadataValidateDetails[metadataItem[sampleNameColumn]] + .foundSampleId + : !metadataValidateDetails[metadataItem[sampleNameColumn]] + .foundSampleId) + ); + }, + [metadataSaveDetails, metadataValidateDetails, sampleNameColumn] + ); + + const samplesUpdatedCount = useMemo( + () => + metadata.filter((metadataItem: MetadataItem) => + filteredSamples(metadataItem, true) + ).length, + [filteredSamples, metadata] + ); - const samplesCreatedCount = metadata.filter( - (metadataItem: MetadataItem) => - metadataSaveDetails[metadataItem.rowKey]?.saved === true && - !metadataValidateDetails[metadataItem.rowKey].locked && - !metadataValidateDetails[metadataItem.rowKey].foundSampleId - ).length; + const samplesCreatedCount = useMemo( + () => + metadata.filter((metadataItem: MetadataItem) => + filteredSamples(metadataItem, false) + ).length, + [filteredSamples, metadata] + ); let stats = samplesUpdatedCount == 1 diff --git a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportReview.tsx b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportReview.tsx index 18248a946f6..1d253d0cda4 100644 --- a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportReview.tsx +++ b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportReview.tsx @@ -4,6 +4,7 @@ import { Alert, Button, List, + notification, Popover, Progress, Table, @@ -75,6 +76,7 @@ export function SampleMetadataImportReview(): JSX.Element { metadata, metadataValidateDetails, metadataSaveDetails, + percentComplete, } = useImportSelector((state: ImportState) => state.importReducer); const dispatch: ImportDispatch = useImportDispatch(); @@ -86,18 +88,15 @@ export function SampleMetadataImportReview(): JSX.Element { }, getCheckboxProps: (record: MetadataItem) => ({ disabled: !( - metadataValidateDetails[record.rowKey].isSampleNameValid || - metadataSaveDetails[record.rowKey]?.saved + metadataValidateDetails[record[sampleNameColumn]].isSampleNameValid || + metadataSaveDetails[record[sampleNameColumn]]?.saved === true ), }), }; React.useEffect(() => { - const savedCount = Object.entries(metadataSaveDetails).filter( - ([, metadataSaveDetailsItem]) => metadataSaveDetailsItem.saved - ).length; - setProgress(Math.round((savedCount / selected.length) * 100)); - }, [metadataSaveDetails, selected.length]); + setProgress(percentComplete); + }, [percentComplete]); React.useEffect(() => { const sampleColumn: ColumnType = { @@ -108,7 +107,8 @@ export function SampleMetadataImportReview(): JSX.Element { onCell: (item) => { return { style: { - background: metadataValidateDetails[item.rowKey].isSampleNameValid + background: metadataValidateDetails[item[sampleNameColumn]] + .isSampleNameValid ? undefined : `var(--red-1)`, }, @@ -121,10 +121,10 @@ export function SampleMetadataImportReview(): JSX.Element { fixed: "left", width: 10, render: (text, item) => { - if (metadataSaveDetails[item.rowKey]?.saved === false) + if (metadataSaveDetails[item[sampleNameColumn]]?.saved === false) return ( @@ -141,7 +141,7 @@ export function SampleMetadataImportReview(): JSX.Element { fixed: "left", width: 70, render: (text, item) => { - if (!metadataValidateDetails[item.rowKey].foundSampleId) + if (!metadataValidateDetails[item[sampleNameColumn]].foundSampleId) return ( {i18n("SampleMetadataImportReview.table.filter.new")} @@ -161,8 +161,10 @@ export function SampleMetadataImportReview(): JSX.Element { ], onFilter: (value, record) => value === "new" - ? metadataValidateDetails[record.rowKey].foundSampleId !== undefined - : metadataValidateDetails[record.rowKey].foundSampleId === undefined, + ? metadataValidateDetails[record[sampleNameColumn]].foundSampleId !== + undefined + : metadataValidateDetails[record[sampleNameColumn]].foundSampleId === + undefined, }; const otherColumns: ColumnsType = headers @@ -184,8 +186,8 @@ export function SampleMetadataImportReview(): JSX.Element { metadata .filter( (row) => - metadataValidateDetails[row.rowKey].isSampleNameValid || - metadataSaveDetails[row.rowKey]?.saved + metadataValidateDetails[row[sampleNameColumn]].isSampleNameValid || + metadataSaveDetails[row[sampleNameColumn]]?.saved === true ) .map((row): string => row.rowKey) ); @@ -204,28 +206,41 @@ export function SampleMetadataImportReview(): JSX.Element { .map((metadataItem) => metadataItem.rowKey); if (projectId) { - const response = await dispatch( - saveMetadata({ projectId, selectedMetadataKeys }) - ).unwrap(); - - if ( - Object.entries(response.metadataSaveDetails).filter( - ([, metadataSaveDetailsItem]) => metadataSaveDetailsItem.error - ).length === 0 - ) { - navigate(`/${projectId}/sample-metadata/upload/complete`); - } else { - setLoading(false); - } + await dispatch(saveMetadata({ projectId, selectedMetadataKeys })) + .unwrap() + .then(({ metadataSaveDetails }) => { + const errorCount = Object.entries(metadataSaveDetails).filter( + ([, metadataSaveDetailsItem]) => metadataSaveDetailsItem.error + ).length; + if (errorCount === 0) { + navigate(`/${projectId}/sample-metadata/upload/complete`); + } else { + setLoading(false); + notification.error({ + message: i18n( + "SampleMetadataImportReview.notification.partialError", + errorCount + ), + }); + } + }) + .catch((payload) => { + setLoading(false); + notification.error({ + message: payload, + className: "t-metadata-uploader-review-error", + }); + }); } }; const isValid = !metadata.some( - (row) => !metadataValidateDetails[row.rowKey].isSampleNameValid + (row) => !metadataValidateDetails[row[sampleNameColumn]].isSampleNameValid ); const lockedSampleMetadata = metadata.filter( - (metadataItem) => metadataValidateDetails[metadataItem.rowKey].locked + (metadataItem) => + metadataValidateDetails[metadataItem[sampleNameColumn]].locked ); return ( @@ -283,12 +298,15 @@ export function SampleMetadataImportReview(): JSX.Element { className="t-metadata-uploader-review-table" rowKey={(row) => row.rowKey} rowClassName={(record) => - metadataSaveDetails[record.rowKey]?.saved === false ? "row-error" : "" + metadataSaveDetails[record[sampleNameColumn]]?.saved === false + ? "row-error" + : "" } rowSelection={rowSelection} columns={columns} dataSource={metadata.filter( - (metadataItem) => !metadataValidateDetails[metadataItem.rowKey].locked + (metadataItem) => + !metadataValidateDetails[metadataItem[sampleNameColumn]].locked )} pagination={getPaginationOptions(metadata.length)} loading={{ diff --git a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportSelectFile.tsx b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportSelectFile.tsx index 22e5e423a3b..2ff7c1b670a 100644 --- a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportSelectFile.tsx +++ b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/components/SampleMetadataImportSelectFile.tsx @@ -36,6 +36,9 @@ export function SampleMetadataImportSelectFile(): JSX.Element { multiple: false, showUploadList: false, accept: ".xls,.xlsx,.csv", + customRequest: () => { + navigate(`/${projectId}/sample-metadata/upload/columns`); + }, beforeUpload: (file: RcFile) => { try { setLoading(true); @@ -64,7 +67,7 @@ export function SampleMetadataImportSelectFile(): JSX.Element { notification.success({ message: i18n("SampleMetadataImportSelectFile.success", file.name), }); - navigate(`/${projectId}/sample-metadata/upload/columns`); + return true; } } catch (error) { setLoading(false); diff --git a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/import-utilities.ts b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/import-utilities.ts new file mode 100644 index 00000000000..3ecb5c92b54 --- /dev/null +++ b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/import-utilities.ts @@ -0,0 +1,70 @@ +import { + CreateSampleItem, + CreateUpdateSamples, + MetadataItem, + UpdateSampleItem, +} from "../../../../apis/projects/samples"; +import { + MetadataHeaderItem, + MetadataSaveDetailsItem, + updatePercentComplete, +} from "./importReducer"; +import { ImportDispatch } from "./store"; +import { chunkArray } from "../../../../utilities/array-utilities"; + +export function generatePromiseList( + sampleList: CreateSampleItem[] | UpdateSampleItem[], + sampleFunction: CreateUpdateSamples, + projectId: string, + totalCount: number, + metadataSaveDetails: Record, + dispatch: ImportDispatch +) { + const newMetadataSaveDetails = { ...metadataSaveDetails }; + const promiseList: Promise[] = []; + const chunkedSampleList = chunkArray(sampleList); + + chunkedSampleList.forEach((chunk) => { + promiseList.push( + sampleFunction({ projectId, body: chunk }).then(({ responses }) => { + dispatch( + updatePercentComplete( + Math.round((Object.keys(responses).length / totalCount) * 100) + ) + ); + Object.keys(responses).forEach((key) => { + const { error, errorMessage } = responses[key]; + newMetadataSaveDetails[key] = { + saved: !error, + error: errorMessage, + }; + }); + }) + ); + }); + + return { promiseList, newMetadataSaveDetails }; +} + +/** + * Create a list of metadata fields and their values for each sample. + * @param sampleNameColumn - the name of the header that represents the sample name column + * @param headers - a list of the table headers + * @param metadataItem - the data of a row in the table representing the metadata of a sample + */ +export function createMetadataFields( + sampleNameColumn: string, + headers: MetadataHeaderItem[], + metadataItem: MetadataItem +) { + return Object.entries(metadataItem) + .filter( + ([key]) => + headers.map((header) => header.name).includes(key) && + key !== sampleNameColumn + ) + .map(([key, value]) => ({ + field: key, + value, + })); +} diff --git a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/importReducer.ts b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/importReducer.ts index b9c9d84b505..06bf508c4a7 100644 --- a/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/importReducer.ts +++ b/src/main/webapp/resources/js/pages/projects/samples-metadata-import/redux/importReducer.ts @@ -5,12 +5,13 @@ import { } from "@reduxjs/toolkit"; import { validateSampleName } from "../../../../apis/metadata/sample-utils"; import { - createSample, - FieldUpdate, + CreateSampleItem, + createSamples, getLockedSamples, LockedSamplesResponse, MetadataItem, - updateSample, + UpdateSampleItem, + updateSamples, ValidateSampleNameModel, validateSamples, ValidateSamplesResponse, @@ -22,6 +23,7 @@ import { MetadataField, } from "../../../../apis/metadata/field"; import { Restriction } from "../../../../utilities/restriction-utilities"; +import { createMetadataFields, generatePromiseList } from "./import-utilities"; export interface MetadataHeaderItem { name: string; @@ -30,21 +32,22 @@ export interface MetadataHeaderItem { rowKey: string; } -interface MetadataValidateDetailsItem { +export interface MetadataValidateDetailsItem { isSampleNameValid: boolean; foundSampleId?: number; locked: boolean; } -interface MetadataSaveDetailsItem { +export interface MetadataSaveDetailsItem { saved: boolean; error?: string; } -interface SaveMetadataResponse { + +export interface SaveMetadataResponse { metadataSaveDetails: Record; } -interface SetSampleNameColumnResponse { +export interface SetSampleNameColumnResponse { sampleNameColumn: string; metadataValidateDetails: Record; } @@ -56,6 +59,7 @@ export interface InitialState { metadata: MetadataItem[]; metadataValidateDetails: Record; metadataSaveDetails: Record; + percentComplete: number; } const initialState: InitialState = { @@ -65,6 +69,7 @@ const initialState: InitialState = { metadata: [], metadataValidateDetails: {}, metadataSaveDetails: {}, + percentComplete: 0, }; /* @@ -74,99 +79,101 @@ For more information on redux async thunks see: https://redux-toolkit.js.org/api export const saveMetadata = createAsyncThunk< SaveMetadataResponse, { projectId: string; selectedMetadataKeys: string[] }, - { dispatch: ImportDispatch; state: ImportState } + { dispatch: ImportDispatch; state: ImportState; rejectValue: string } >( `importReducer/saveMetadata`, - async ({ projectId, selectedMetadataKeys }, { dispatch, getState }) => { + async ( + { projectId, selectedMetadataKeys }, + { dispatch, getState, rejectWithValue } + ) => { const state: ImportState = getState(); - const { sampleNameColumn, headers, metadata, metadataValidateDetails } = - state.importReducer; - const metadataSaveDetails: Record = {}; + const { + sampleNameColumn, + headers, + metadata, + metadataValidateDetails, + metadataSaveDetails, + } = state.importReducer; - await createMetadataFieldsForProject({ - projectId, - body: headers - .filter((header) => header.name !== sampleNameColumn) - .map((header) => ({ - label: header.name, - restriction: header.targetRestriction, - })), - }); + try { + //save header details (metadata field & restriction) + //if failure display error notification on page + await createMetadataFieldsForProject({ + projectId, + body: headers + .filter((header) => header.name !== sampleNameColumn) + .map((header) => ({ + label: header.name, + restriction: header.targetRestriction, + })), + }).catch((error) => { + throw new Error(error.response.data.error); + }); - const chunkSize = 100; - for (let i = 0; i < metadata.length; i = i + chunkSize) { - const promises: Promise[] = []; - for (let j = i; j < i + chunkSize && j < metadata.length; j++) { - const metadataItem: MetadataItem = metadata[j]; - const index: string = metadataItem.rowKey; - if ( - selectedMetadataKeys.includes(index) && - metadataSaveDetails[index]?.saved !== true - ) { - const name: string = metadataItem[sampleNameColumn]; - const metadataFields: FieldUpdate[] = Object.entries(metadataItem) - .filter( - ([key]) => - headers.map((header) => header.name).includes(key) && - key !== sampleNameColumn - ) - .map(([key, value]) => ({ - field: key, - value, - restriction: headers.filter((header) => header.name === key)[0] - .targetRestriction, - })); - const sampleId = metadataValidateDetails[index].foundSampleId; - if (sampleId) { - promises.push( - updateSample({ - projectId, - sampleId, - body: { - name, - metadata: metadataFields, - }, - }) - .then(() => { - metadataSaveDetails[index] = { saved: true }; - }) - .catch((error) => { - metadataSaveDetails[index] = { - saved: false, - error: error.response.data.error, - }; - }) - ); - } else { - promises.push( - createSample({ - projectId, - body: { - name, - metadata: metadataFields, - }, - }) - .then(() => { - metadataSaveDetails[index] = { saved: true }; - }) - .catch((error) => { - metadataSaveDetails[index] = { - saved: false, - error: error.response.data.error, - }; - }) - ); - } - } - } - await Promise.all(promises).then(() => { - dispatch( - setMetadataSaveDetails(Object.assign({}, metadataSaveDetails)) + //save selected metadata entry rows + //during a partial failure only save data that has not already been saved + const selectedSampleList = metadata.filter((metadataItem) => { + const name: string = metadataItem[sampleNameColumn]; + return ( + selectedMetadataKeys.includes(metadataItem.rowKey) && + metadataSaveDetails[name]?.saved !== true ); }); - } - return { metadataSaveDetails }; + const createSampleList: CreateSampleItem[] = []; + const updateSampleList: UpdateSampleItem[] = []; + + selectedSampleList.forEach((metadataItem) => { + const name = metadataItem[sampleNameColumn]; + const sampleId = metadataValidateDetails[name].foundSampleId; + const metadata = createMetadataFields( + sampleNameColumn, + headers, + metadataItem + ); + + if (typeof sampleId === "number") { + updateSampleList.push({ name, sampleId, metadata }); + } else { + createSampleList.push({ name, metadata }); + } + }); + + const { + promiseList: createPromiseList, + newMetadataSaveDetails: createMetadataSaveDetails, + } = generatePromiseList( + createSampleList, + createSamples, + projectId, + selectedSampleList.length, + metadataSaveDetails, + dispatch + ); + await Promise.all(createPromiseList); + + const { + promiseList: updatePromiseList, + newMetadataSaveDetails: updateMetadataSaveDetails, + } = generatePromiseList( + updateSampleList, + updateSamples, + projectId, + selectedSampleList.length, + createMetadataSaveDetails, + dispatch + ); + await Promise.all(updatePromiseList); + return { metadataSaveDetails: updateMetadataSaveDetails }; + } catch (error) { + let message; + if (error instanceof Error) { + ({ message } = error); + } else { + message = String(error); + } + return rejectWithValue(message); + } } ); @@ -200,7 +207,6 @@ export const setSampleNameColumn = createAsyncThunk< projectId, }); for (const metadataItem of metadata) { - const index: string = metadataItem.rowKey; const sampleName: string = metadataItem[updatedSampleNameColumn]; const foundValidatedSamples = validatedSamples.samples.find( (sample) => sampleName === sample.name @@ -209,7 +215,7 @@ export const setSampleNameColumn = createAsyncThunk< const foundLockedSamples = lockedSamples.sampleIds.find( (sampleId) => sampleId === foundSampleId ); - metadataValidateDetails[index] = { + metadataValidateDetails[sampleName] = { isSampleNameValid: validateSampleName(sampleName), foundSampleId: foundSampleId, locked: !!foundLockedSamples, @@ -304,6 +310,17 @@ export const setMetadataSaveDetails = createAction( }) ); +/* +Redux action for updating the progress bar. +For more information on redux actions see: https://redux-toolkit.js.org/api/createAction + */ +export const updatePercentComplete = createAction( + `importReducer/updatePercentComplete`, + (amount: number) => ({ + payload: { amount }, + }) +); + /* Redux reducer for project metadata. For more information on redux reducers see: https://redux-toolkit.js.org/api/createReducer @@ -319,6 +336,7 @@ export const importReducer = createReducer(initialState, (builder) => { state.metadata = []; state.metadataValidateDetails = {}; state.metadataSaveDetails = {}; + state.percentComplete = 0; }); builder.addCase(setMetadata, (state, action) => { state.metadata = action.payload.metadata; @@ -336,4 +354,7 @@ export const importReducer = createReducer(initialState, (builder) => { builder.addCase(saveMetadata.fulfilled, (state, action) => { state.metadataSaveDetails = action.payload.metadataSaveDetails; }); + builder.addCase(updatePercentComplete, (state, action) => { + state.percentComplete = state.percentComplete + action.payload.amount; + }); }); diff --git a/src/main/webapp/resources/js/pages/projects/samples/components/CreateNewSample.jsx b/src/main/webapp/resources/js/pages/projects/samples/components/CreateNewSample.jsx index 5e60a48f584..1ddc792c3d4 100644 --- a/src/main/webapp/resources/js/pages/projects/samples/components/CreateNewSample.jsx +++ b/src/main/webapp/resources/js/pages/projects/samples/components/CreateNewSample.jsx @@ -1,7 +1,7 @@ import React from "react"; import { AutoComplete, Form, Input, Modal } from "antd"; import { - createNewSample, + createSamples, validateSampleName, } from "../../../../apis/projects/samples"; import searchOntology from "../../../../apis/ontology/taxonomy/query"; @@ -11,7 +11,12 @@ import searchOntology from "../../../../apis/ontology/taxonomy/query"; * @returns {JSX.Element} * @constructor */ -export default function CreateNewSample({ visible, onCreate, onCancel }) { +export default function CreateNewSample({ + visible, + projectId, + onCreate, + onCancel, +}) { const [form] = Form.useForm(); const nameRef = React.useRef(); const [organisms, setOrganisms] = React.useState([]); @@ -72,7 +77,10 @@ export default function CreateNewSample({ visible, onCreate, onCancel }) { const createSample = () => { form.validateFields().then((values) => { - createNewSample(values).then(() => { + createSamples({ + projectId, + body: [values], + }).then(() => { form.resetFields(); onCreate(); }); diff --git a/src/main/webapp/resources/js/pages/projects/samples/components/SamplesMenu.jsx b/src/main/webapp/resources/js/pages/projects/samples/components/SamplesMenu.jsx index a3a1a2f8dfc..904ad959c65 100644 --- a/src/main/webapp/resources/js/pages/projects/samples/components/SamplesMenu.jsx +++ b/src/main/webapp/resources/js/pages/projects/samples/components/SamplesMenu.jsx @@ -355,6 +355,7 @@ export default function SamplesMenu() { }> setCreateSampleVisible(false)} onCreate={onCreate} /> diff --git a/src/main/webapp/resources/js/utilities/array-utilities.ts b/src/main/webapp/resources/js/utilities/array-utilities.ts new file mode 100644 index 00000000000..7454ddb8b5b --- /dev/null +++ b/src/main/webapp/resources/js/utilities/array-utilities.ts @@ -0,0 +1,33 @@ +/** + * Splits an array into multiple, smaller arrays + * + * @param items the array to be chunked + * + * @returns a chunked array + */ +export function chunkArray(items: T[]) { + const size = calculateChunkSize(items.length); + const clone = [...items]; + const chunks = []; + + while (clone.length) { + chunks.push(clone.splice(0, size)); + } + + return chunks; +} + +/** + * Calculate the best request chunk size + * + * @param arraySize the size of the array + * + * @returns the chunk size + */ +function calculateChunkSize(arraySize: number) { + const MIN = 10; + const MAX = 100; + const estimated = Math.ceil(arraySize / 10); + const isAboveMax = estimated > MAX ? MAX : estimated; + return estimated < MIN ? MIN : isAboveMax; +} diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/junit5/listeners/IntegrationUITestListener.java b/src/test/java/ca/corefacility/bioinformatics/irida/junit5/listeners/IntegrationUITestListener.java index 80d7731762a..ce472458a41 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/junit5/listeners/IntegrationUITestListener.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/junit5/listeners/IntegrationUITestListener.java @@ -1,5 +1,13 @@ package ca.corefacility.bioinformatics.irida.junit5.listeners; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + import org.apache.commons.io.FileUtils; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -10,14 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - import static org.junit.jupiter.api.Assertions.fail; public class IntegrationUITestListener implements TestExecutionListener { @@ -25,12 +25,13 @@ public class IntegrationUITestListener implements TestExecutionListener { private static final Logger logger = LoggerFactory.getLogger(IntegrationUITestListener.class); public static final int DRIVER_TIMEOUT_IN_SECONDS = 3; - private static final File TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir")); public static final File DOWNLOAD_DIRECTORY = new File(TEMP_DIRECTORY, "irida-test"); private static WebDriver driver; + private static WebDriver driver2; + /** * {@inheritDoc} */ @@ -58,6 +59,15 @@ public static WebDriver driver() { return driver; } + /** + * Get a reference to the second {@link WebDriver} used in the tests. + * + * @return the second instance of {@link WebDriver} used in the tests. + */ + public static WebDriver driver2() { + return driver2; + } + /** * Start the web driver. */ @@ -99,18 +109,20 @@ public static void startWebDriver() { if (seleniumUrl != null) { try { driver = new RemoteWebDriver(new URL(seleniumUrl), options); + driver2 = new RemoteWebDriver(new URL(seleniumUrl), options); } catch (MalformedURLException e) { logger.error("webdriver.selenium_url is malformed", e); fail("Could not connect to the remote web driver at following url: " + seleniumUrl); } } else { driver = new ChromeDriver(options); + driver2 = new ChromeDriver(options); } driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(DRIVER_TIMEOUT_IN_SECONDS)); + driver2.manage().timeouts().implicitlyWait(Duration.ofSeconds(DRIVER_TIMEOUT_IN_SECONDS)); } - public static void stopWebDriver() { // Clean up the download directory try { @@ -120,5 +132,6 @@ public static void stopWebDriver() { } driver.quit(); + driver2.quit(); } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/AbstractIridaUIITChromeDriver.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/AbstractIridaUIITChromeDriver.java index a6d04707d49..0500629bec0 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/AbstractIridaUIITChromeDriver.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/AbstractIridaUIITChromeDriver.java @@ -89,8 +89,9 @@ public static WebDriver driver() { final String chromeDriverProp = System.getProperty(CHROMEDRIVER_PROP_KEY); System.setProperty(CHROMEDRIVER_PROP_KEY, Strings.isNullOrEmpty(chromeDriverProp) ? CHROMEDRIVER_LOCATION : chromeDriverProp); - logger.debug("Starting ChromeDriver for a single test class. Using `chromedriver` at '" - + System.getProperty(CHROMEDRIVER_PROP_KEY) + "'"); + logger.debug( + "Starting ChromeDriver for a single test class. Using `chromedriver` at '" + System.getProperty( + CHROMEDRIVER_PROP_KEY) + "'"); isSingleTest = true; IntegrationUITestListener.startWebDriver(); } @@ -99,6 +100,26 @@ public static WebDriver driver() { } + /** + * Get a reference to the {@link WebDriver} used in the tests. + * + * @return the instance of {@link WebDriver} used in the tests. + */ + public static WebDriver driver2() { + if (IntegrationUITestListener.driver2() == null) { + final String chromeDriverProp = System.getProperty(CHROMEDRIVER_PROP_KEY); + System.setProperty(CHROMEDRIVER_PROP_KEY, + Strings.isNullOrEmpty(chromeDriverProp) ? CHROMEDRIVER_LOCATION : chromeDriverProp); + logger.debug("Starting a second ChromeDriver for a single test class. Using `chromedriver` at '" + + System.getProperty(CHROMEDRIVER_PROP_KEY) + "'"); + isSingleTest = true; + IntegrationUITestListener.startWebDriver(); + } + + return IntegrationUITestListener.driver2(); + + } + /** * Method to use on any page to check to ensure that internationalization messages are being automatically loaded * onto the page. @@ -135,8 +156,9 @@ public void testFailed(ExtensionContext context, Throwable t) { final Path screenshot = Paths.get(takesScreenshot.getScreenshotAs(OutputType.FILE).toURI()); try { - final Path destination = Files.createTempFile("irida-" + context.getRequiredTestClass().getSimpleName() - + "#" + context.getRequiredTestMethod().getName(), ".png"); + final Path destination = Files.createTempFile( + "irida-" + context.getRequiredTestClass().getSimpleName() + "#" + + context.getRequiredTestMethod().getName(), ".png"); Files.move(screenshot, destination, StandardCopyOption.REPLACE_EXISTING); logger.info("Screenshot deposited at: [" + destination.toString() + "]"); } catch (final IOException e) { diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/CartPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/CartPageIT.java index e718bba1839..d2482371a3b 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/CartPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/CartPageIT.java @@ -1,5 +1,18 @@ package ca.corefacility.bioinformatics.irida.ria.integration; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + import ca.corefacility.bioinformatics.irida.ria.integration.components.FastQCModal; import ca.corefacility.bioinformatics.irida.ria.integration.components.SampleDetailsViewer; import ca.corefacility.bioinformatics.irida.ria.integration.pages.LoginPage; @@ -7,20 +20,9 @@ import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSamplesPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.TableSummary; import ca.corefacility.bioinformatics.irida.ria.integration.utilities.FileUtilities; + import com.github.springtestdbunit.annotation.DatabaseSetup; import com.google.common.collect.ImmutableList; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.JavascriptExecutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -101,7 +103,7 @@ public void testCartPageAsUser() { LoginPage.loginAsUser(driver()); driver().manage().window().maximize(); // Add some samples to the cart and test to see if they get displayed/ - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); SampleDetailsViewer sampleDetailsViewer = SampleDetailsViewer.getSampleDetails(driver()); @@ -159,7 +161,8 @@ public void testCartPageAsUser() { assertEquals(sampleName, sampleDetailsViewer.getSampleName(), "Should be viewing the proper sample"); assertEquals(projectName, sampleDetailsViewer.getProjectName(), "Should have proper project name displayed for sample"); - assertEquals("Jul 19, 2013, 2:18 PM", sampleDetailsViewer.getCreatedDateForSample(), "Should display the correct created date"); + assertEquals("Jul 19, 2013, 2:18 PM", sampleDetailsViewer.getCreatedDateForSample(), + "Should display the correct created date"); sampleDetailsViewer.clickMetadataTabLink(); assertFalse(sampleDetailsViewer.addNewMetadataButtonVisible()); @@ -214,7 +217,8 @@ public void testCartPageAsUser() { sampleDetailsViewer.clickRemoveSampleFromCartButtonCartPage(); - assertFalse(sampleDetailsViewer.sampleDetailsViewerVisible(), "The sample details viewer should not be displayed as the sample was removed from the cart"); + assertFalse(sampleDetailsViewer.sampleDetailsViewerVisible(), + "The sample details viewer should not be displayed as the sample was removed from the cart"); // Test removing a sample from the project page.removeSampleFromCart(0); @@ -229,7 +233,7 @@ public void testCartPageAsAdmin() { LoginPage.loginAsAdmin(driver()); driver().manage().window().maximize(); // Add some samples to the cart and test to see if they get displayed/ - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); SampleDetailsViewer sampleDetailsViewer = SampleDetailsViewer.getSampleDetails(driver()); // Test that the add/remove buttons for the sample detail viewer are displayed correctly depending on if the sample is in the cart or not @@ -258,7 +262,6 @@ public void testCartPageAsAdmin() { sampleDetailsViewer.clickRemoveSampleFromCartButton(); sampleDetailsViewer.clickSampleDetailsViewerCloseButton(); - samplesPage.selectSampleByName("sample5fg44"); samplesPage.selectSampleByName("sample5fdgr"); samplesPage.selectSampleByName("sample554sg5"); @@ -279,7 +282,6 @@ public void testCartPageAsAdmin() { final String projectName = "project"; page.viewSampleDetailsFor(sampleName); - assertFalse(sampleDetailsViewer.isAddSampleToCartButtonVisible(), "The add cart to sample button should not be displayed"); assertTrue(sampleDetailsViewer.isRemoveSampleFromCartButtonVisible(), @@ -289,7 +291,8 @@ public void testCartPageAsAdmin() { assertEquals(projectName, sampleDetailsViewer.getProjectName(), "Should have proper project name displayed for sample"); - assertEquals("Jul 19, 2013, 2:18 PM", sampleDetailsViewer.getCreatedDateForSample(), "Should display the correct created date"); + assertEquals("Jul 19, 2013, 2:18 PM", sampleDetailsViewer.getCreatedDateForSample(), + "Should display the correct created date"); sampleDetailsViewer.clickMetadataTabLink(); assertTrue(sampleDetailsViewer.addNewMetadataButtonVisible()); @@ -396,14 +399,18 @@ public void testCartPageAsAdmin() { "Should not have any concatenation checkboxes"); assertEquals(7, sampleDetailsViewer.actionButtonsVisible(), "Should have 7 file action buttons"); - assertEquals(1, sampleDetailsViewer.numberOfSequencingObjectsSetAsDefault(), "One sequencing object should have a default tag"); - assertEquals(2, sampleDetailsViewer.numberOfSetAsDefaultSeqObjsButtons(), "There should be two set as default buttons for sequencing objects"); + assertEquals(1, sampleDetailsViewer.numberOfSequencingObjectsSetAsDefault(), + "One sequencing object should have a default tag"); + assertEquals(2, sampleDetailsViewer.numberOfSetAsDefaultSeqObjsButtons(), + "There should be two set as default buttons for sequencing objects"); // Remove the 5 remaining files (1 single end sequencing object and 2 paired end sequencing objects containing 2 files each, and 2 assemblies) js.executeScript("document.getElementsByClassName('t-filelist-scroll')[0].scrollTop= 600"); - assertEquals(1, sampleDetailsViewer.numberOfGenomeAssembliesSetAsDefault(), "One sequencing object should have a default tag"); - assertEquals(1, sampleDetailsViewer.numberOfGenomeAssembliesSetAsDefaultButtons(), "There should be one set as default button for sequencing objects"); + assertEquals(1, sampleDetailsViewer.numberOfGenomeAssembliesSetAsDefault(), + "One sequencing object should have a default tag"); + assertEquals(1, sampleDetailsViewer.numberOfGenomeAssembliesSetAsDefaultButtons(), + "There should be one set as default button for sequencing objects"); sampleDetailsViewer.removeFile(6); sampleDetailsViewer.removeFile(5); @@ -419,7 +426,8 @@ public void testCartPageAsAdmin() { sampleDetailsViewer.clickRemoveSampleFromCartButtonCartPage(); - assertFalse(sampleDetailsViewer.sampleDetailsViewerVisible(), "The sample details viewer should not be displayed as the sample was removed from the cart"); + assertFalse(sampleDetailsViewer.sampleDetailsViewerVisible(), + "The sample details viewer should not be displayed as the sample was removed from the cart"); // Test removing a sample from the project page.removeSampleFromCart(0); @@ -461,11 +469,10 @@ void addAndRemoveAssociatedProjectSamplesToCart() { LoginPage.loginAsAdmin(driver()); driver().manage().window().maximize(); - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.toggleAssociatedProject(PROJECT_NAME); TableSummary summary = samplesPage.getTableSummary(); - assertEquals(22, summary.getTotal(), - "Should have more samples visible with another project selected"); + assertEquals(22, summary.getTotal(), "Should have more samples visible with another project selected"); samplesPage.selectSampleByName(ASSOCIATED_SAMPLE_NAME); samplesPage.selectSampleByName(SAMPLE_NAME); samplesPage.addSelectedSamplesToCart(); @@ -478,7 +485,8 @@ void addAndRemoveAssociatedProjectSamplesToCart() { viewer.clickRemoveSampleFromCartButtonCartPage(); - assertFalse(viewer.sampleDetailsViewerVisible(), "The sample details viewer should not be displayed as the sample was removed from the cart"); + assertFalse(viewer.sampleDetailsViewerVisible(), + "The sample details viewer should not be displayed as the sample was removed from the cart"); assertEquals(1, cartPage.getNumberOfSamplesInCart(), "Should only be 1 sample in the cart"); } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/components/AddMemberButton.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/components/AddMemberButton.java index 14d46aec874..00cbd9631bf 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/components/AddMemberButton.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/components/AddMemberButton.java @@ -33,21 +33,6 @@ public static AddMemberButton getAddMemberButton(WebDriver driver) { return PageFactory.initElements(driver, AddMemberButton.class); } - public void addMember(WebDriver driver, String name) { - wait.until(ExpectedConditions.elementToBeClickable(addMemberBtn)); - addMemberBtn.click(); - wait.until(ExpectedConditions.visibilityOf(addMemberModal)); - AbstractPage.waitForTime(100); - WebElement input = driver.switchTo().activeElement(); - input.sendKeys(name); - wait.until(ExpectedConditions.visibilityOf(newMemberList.get(0))); - newMemberList.get(0).click(); - WebElement modalOkBtn = addMemberModal.findElement(By.cssSelector(".ant-btn.ant-btn-primary")); - wait.until(ExpectedConditions.elementToBeClickable(modalOkBtn)); - modalOkBtn.click(); - wait.until(ExpectedConditions.invisibilityOf(addMemberModal)); - } - public void addMember(WebDriver driver, String name, String role) { wait.until(ExpectedConditions.elementToBeClickable(addMemberBtn)); addMemberBtn.click(); @@ -57,11 +42,19 @@ public void addMember(WebDriver driver, String name, String role) { input.sendKeys(name); wait.until(ExpectedConditions.visibilityOf(newMemberList.get(0))); newMemberList.get(0).click(); - if(role.equals("GROUP_OWNER")) { + if (role.equals("PROJECT_OWNER")) { + WebElement element = driver.findElements(By.className("t-project-role-manager")).get(0); + wait.until(ExpectedConditions.elementToBeClickable(element)); + element.click(); + } else if (role.equals("PROJECT_USER")) { + WebElement element = driver.findElements(By.className("t-project-role-collaborator")).get(0); + wait.until(ExpectedConditions.elementToBeClickable(element)); + element.click(); + } else if (role.equals("GROUP_OWNER")) { WebElement element = driver.findElements(By.className("t-group-role-owner")).get(0); wait.until(ExpectedConditions.elementToBeClickable(element)); element.click(); - } else { + } else if (role.equals("GROUP_MEMBER")) { WebElement element = driver.findElements(By.className("t-group-role-member")).get(0); wait.until(ExpectedConditions.elementToBeClickable(element)); element.click(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/groups/UserGroupsIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/groups/UserGroupsIT.java index 3a7550889b9..a82623e069f 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/groups/UserGroupsIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/groups/UserGroupsIT.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import ca.corefacility.bioinformatics.irida.model.enums.ProjectRole; import ca.corefacility.bioinformatics.irida.ria.integration.AbstractIridaUIITChromeDriver; import ca.corefacility.bioinformatics.irida.ria.integration.pages.LoginPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.ProjectMembersPage; @@ -32,7 +33,8 @@ public void testUserGroupsAsManager() { final String PRE_CREATION_URL = driver().getCurrentUrl(); listingPage.createNewUserGroup(GROUP_NAME); listingPage.validateRouteChange(PRE_CREATION_URL); - assertFalse(driver().getCurrentUrl().contains("/admin/groups"), "Does not redirect to admin panel user details page"); + assertFalse(driver().getCurrentUrl().contains("/admin/groups"), + "Does not redirect to admin panel user details page"); assertTrue(driver().getCurrentUrl().contains("/groups"), "Redirects user to main app user details page"); UserGroupsDetailsPage detailsPage = UserGroupsDetailsPage.initPage(driver()); @@ -56,7 +58,8 @@ public void testUserGroupsAsManager() { final String PRE_DELETION_URL = driver().getCurrentUrl(); detailsPage.deleteGroup(); listingPage.validateRouteChange(PRE_DELETION_URL); - assertFalse(driver().getCurrentUrl().endsWith("/admin/groups"), "Does not redirect to admin panel user groups page"); + assertFalse(driver().getCurrentUrl().endsWith("/admin/groups"), + "Does not redirect to admin panel user groups page"); assertTrue(driver().getCurrentUrl().endsWith("/groups"), "Redirects user to main app user groups page"); assertEquals(2, listingPage.getNumberOfExistingUserGroups(), "Should have 2 user groups"); } @@ -99,7 +102,8 @@ public void testUserGroupsAsAdmin() { final String PRE_DELETION_URL = driver().getCurrentUrl(); detailsPage.deleteGroup(); listingPage.validateRouteChange(PRE_DELETION_URL); - assertTrue(driver().getCurrentUrl().endsWith("/admin/groups"), "Redirects user to admin panel user groups page"); + assertTrue(driver().getCurrentUrl().endsWith("/admin/groups"), + "Redirects user to admin panel user groups page"); assertEquals(2, listingPage.getNumberOfExistingUserGroups(), "Should have 2 user groups"); } @@ -110,7 +114,7 @@ public void testAddGroupMemberWhenManagerOnMemberProjectAsCollaborator() { LoginPage.loginAsUser(driver()); ProjectMembersPage projectMembersPage = ProjectMembersPage.goToRemoteProject(driver(), PROJECT_ID); // Add manager as collaborator on project - projectMembersPage.addUserToProject("mrtest"); + projectMembersPage.addUserToProject("mrtest", ProjectRole.PROJECT_USER.toString()); LoginPage.logout(driver()); // Login as manager and create a new group @@ -157,7 +161,8 @@ public void testAddUserGroupMemberWithSelectedRole() { final String PRE_CREATION_URL = driver().getCurrentUrl(); listingPage.createNewUserGroup(GROUP_NAME); listingPage.validateRouteChange(PRE_CREATION_URL); - assertFalse(driver().getCurrentUrl().contains("/admin/groups"), "Does not redirect to admin panel user details page"); + assertFalse(driver().getCurrentUrl().contains("/admin/groups"), + "Does not redirect to admin panel user details page"); assertTrue(driver().getCurrentUrl().contains("/groups"), "Redirects user to main app user details page"); UserGroupsDetailsPage detailsPage = UserGroupsDetailsPage.initPage(driver()); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/ProjectMembersPage.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/ProjectMembersPage.java index 823775c5a89..d92c8000896 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/ProjectMembersPage.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/ProjectMembersPage.java @@ -1,7 +1,8 @@ package ca.corefacility.bioinformatics.irida.ria.integration.pages; -import ca.corefacility.bioinformatics.irida.ria.integration.components.AddMemberButton; -import ca.corefacility.bioinformatics.irida.ria.integration.components.AntTable; +import java.time.Duration; +import java.util.List; + import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -10,8 +11,8 @@ import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; -import java.time.Duration; -import java.util.List; +import ca.corefacility.bioinformatics.irida.ria.integration.components.AddMemberButton; +import ca.corefacility.bioinformatics.irida.ria.integration.components.AntTable; /** *

@@ -103,8 +104,8 @@ public boolean isNotificationDisplayed() { return true; } - public void addUserToProject(String name) { - addMemberButton.addMember(driver, name); + public void addUserToProject(String name, String role) { + addMemberButton.addMember(driver, name, role); } public boolean isAddMemberBtnVisible() { diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/NcbiExportPage.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/NcbiExportPage.java index 0c52f7691a8..9c52713cb3f 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/NcbiExportPage.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/NcbiExportPage.java @@ -201,7 +201,7 @@ public boolean isSuccessAlertDisplayed() { return driver.findElements(By.cssSelector(".ant-alert.ant-alert-success")).size() == 1; } - public boolean isUserRedirectedToProjectSamplesPage(int projectId) { + public boolean isUserRedirectedToProjectSamplesPage(Long projectId) { WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); return wait.until(ExpectedConditions.urlMatches("/projects/" + projectId)); } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSampleMetadataImportPage.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSampleMetadataImportPage.java index 85eb7a5fd03..471c6b2427e 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSampleMetadataImportPage.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSampleMetadataImportPage.java @@ -42,10 +42,14 @@ public class ProjectSampleMetadataImportPage extends AbstractPage { List headers; @FindBy(css = "tbody tr.ant-table-row") List rows; + @FindBy(className = "anticon-exclamation-circle") + List tableErrors; @FindBy(css = "div.ant-alert-error") WebElement validationAlert; @FindBy(css = "div.ant-result-success") WebElement successMessage; + @FindBy(className = "t-metadata-uploader-review-error") + WebElement errorNotification; public ProjectSampleMetadataImportPage(WebDriver driver) { super(driver); @@ -57,22 +61,22 @@ public static ProjectSampleMetadataImportPage goToPage(WebDriver driver) { } public void uploadMetadataFile(String filePath) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); Path path = Paths.get(filePath); dropzone.sendKeys(path.toAbsolutePath().toString()); - wait.until(ExpectedConditions.visibilityOf(fileBtn)); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); + wait.until(ExpectedConditions.urlContains("/columns")); } public void goToReviewPage() { previewBtn.click(); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); - wait.until(ExpectedConditions.visibilityOf(reviewTable)); + wait.until(ExpectedConditions.urlContains("/review")); } public void goToCompletePage() { uploadBtn.click(); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); - wait.until(ExpectedConditions.visibilityOf(successMessage)); + wait.until(ExpectedConditions.urlContains("/complete")); } public void selectSampleNameColumn(String sampleNameColumn) { @@ -107,6 +111,14 @@ public List getValuesForColumnByName(String column) { .collect(Collectors.toList()); } + public void clickUploadButton() { + uploadBtn.click(); + } + + public boolean hasTableErrors() { + return !tableErrors.isEmpty(); + } + public boolean isAlertDisplayed() { return validationAlert.isDisplayed(); } @@ -114,4 +126,11 @@ public boolean isAlertDisplayed() { public boolean isSuccessDisplayed() { return successMessage.isDisplayed(); } + + public boolean isErrorNotificationDisplayed() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); + wait.until(ExpectedConditions.visibilityOf(errorNotification)); + return true; + } + } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSamplesPage.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSamplesPage.java index dad7ab10fb4..5be755a86ec 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSamplesPage.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pages/projects/ProjectSamplesPage.java @@ -1,5 +1,12 @@ package ca.corefacility.bioinformatics.irida.ria.integration.pages.projects; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; @@ -9,456 +16,455 @@ import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - /** *

* Page Object to represent the project samples page. *

*/ public class ProjectSamplesPage extends ProjectPageBase { - private static final String RELATIVE_URL = "projects/"; + private static final String RELATIVE_URL = "projects/"; + + @FindBy(tagName = "h1") + private WebElement pageHeader; + + @FindBy(className = "t-sample-tools") + private WebElement toolsDropdownBtn; + + @FindBy(className = "t-tools-dropdown") + private WebElement toolsDropdown; + + @FindBy(className = "t-merge") + private WebElement mergeBtn; + + @FindBy(className = "t-share") + private WebElement shareBtn; + + @FindBy(className = "t-remove") + private WebElement removeBtn; + + @FindBy(className = "t-export") + private WebElement exportSamplesDropdownBtn; + + @FindBy(className = "t-export-dropdown") + private WebElement exportDropdown; + + @FindBy(className = "t-download") + private WebElement downloadBtn; + + @FindBy(className = "t-linker") + private WebElement linkerBtn; + + @FindBy(className = "t-ncbi") + private WebElement ncbiExportBtn; + + @FindBy(className = "t-create-sample") + private WebElement createSampleButton; + + @FindBy(className = "t-linker-modal") + private WebElement linkerModal; + + @FindBy(className = "t-cmd-text") + private WebElement linkerCmd; + + @FindBy(className = "t-summary") + private WebElement tableSummary; + + // FILTERS + @FindBy(css = ".t-td-name .ant-table-filter-trigger") + private WebElement sampleNameFilterToggle; + + @FindBy(css = ".t-name-select input") + private WebElement nameFilterInput; - @FindBy(tagName = "h1") - private WebElement pageHeader; + @FindBy(css = ".t-name-select .ant-select-selector") + private WebElement nameFilterSelectedOptions; - @FindBy(className = "t-sample-tools") - private WebElement toolsDropdownBtn; + @FindBy(css = ".t-td-organism .ant-table-filter-trigger") + private WebElement organismFilterToggle; - @FindBy(className = "t-tools-dropdown") - private WebElement toolsDropdown; + @FindBy(css = ".t-organism-select input") + private WebElement organismSelectInput; - @FindBy(className = "t-merge") - private WebElement mergeBtn; + @FindBy(css = ".t-organism-select .ant-select-selector") + private WebElement organismFilterSelectedOptions; - @FindBy(className = "t-share") - private WebElement shareBtn; + @FindBy(css = ".t-td-project .ant-dropdown-trigger") + private WebElement projectsFilterToggle; - @FindBy(className = "t-remove") - private WebElement removeBtn; + @FindBy(css = ".t-td-created .ant-table-filter-trigger") + private WebElement createdDateFilterToggle; - @FindBy(className = "t-export") - private WebElement exportSamplesDropdownBtn; + @FindBy(className = "t-created-filter") + private WebElement createdDateFilter; - @FindBy(className = "t-export-dropdown") - private WebElement exportDropdown; + @FindBy(css = ".t-td-modified .ant-table-filter-trigger") + private WebElement modifiedDateFilterToggle; - @FindBy(className = "t-download") - private WebElement downloadBtn; + @FindBy(className = "t-modified-filter") + private WebElement modifiedDateFilter; - @FindBy(className = "t-linker") - private WebElement linkerBtn; + @FindBy(className = "t-samples-table") + private WebElement samplesTable; - @FindBy(className = "t-ncbi") - private WebElement ncbiExportBtn; + @FindBy(css = ".t-select-all input") + private WebElement selectAllCheckbox; - @FindBy(className = "t-create-sample") - private WebElement createSampleButton; + @FindBy(className = "t-merge-modal") + private WebElement mergeModal; - @FindBy(className = "t-linker-modal") - private WebElement linkerModal; + //----- OLD BELOW - @FindBy(className = "t-cmd-text") - private WebElement linkerCmd; + @FindBy(className = "t-associated-btn") + private WebElement associatedProjectMenuBtn; - @FindBy(className = "t-summary") - private WebElement tableSummary; + @FindBy(className = "t-associated-dropdown") + private WebElement associatedDropdown; - // FILTERS - @FindBy(css = ".t-td-name .ant-table-filter-trigger") - private WebElement sampleNameFilterToggle; + @FindBy(className = "t-associated-cb") + private List associatedCbs; - @FindBy(css = ".t-name-select input") - private WebElement nameFilterInput; + @FindBy(className = "selected-counts") + private WebElement selectedCountInfo; - @FindBy(css = ".t-name-select .ant-select-selector") - private WebElement nameFilterSelectedOptions; + @FindBy(id = "samplesTable_info") + private WebElement samplesTableInfo; - @FindBy(css = ".t-td-organism .ant-table-filter-trigger") - private WebElement organismFilterToggle; + @FindBy(css = "tbody tr") + private List tableRows; - @FindBy(css = ".t-organism-select input") - private WebElement organismSelectInput; + @FindBy(id = "giveOwner") + private WebElement giveOwnerBtn; - @FindBy(css = ".t-organism-select .ant-select-selector") - private WebElement organismFilterSelectedOptions; + @FindBy(className = "t-move-btn") + private WebElement moveBtn; - @FindBy(css = ".t-td-project .ant-dropdown-trigger") - private WebElement projectsFilterToggle; + @FindBy(className = "t-add-cart-btn") + private WebElement addToCartBtn; - @FindBy(css = ".t-td-created .ant-table-filter-trigger") - private WebElement createdDateFilterToggle; + @FindBy(className = "t-remove-modal") + private WebElement removeModal; - @FindBy(className = "t-created-filter") - private WebElement createdDateFilter; + @FindBy(id = "confirmMergeBtn") + private WebElement mergeBtnOK; - @FindBy(css = ".t-td-modified .ant-table-filter-trigger") - private WebElement modifiedDateFilterToggle; + @FindBy(id = "sampleName") + private WebElement newMergeNameInput; - @FindBy(className = "t-modified-filter") - private WebElement modifiedDateFilter; + @FindBy(className = "t-copy-samples-modal") + private WebElement copySamplesModal; - @FindBy(className = "t-samples-table") - private WebElement samplesTable; + @FindBy(id = "js-confirm") + private WebElement copyModalConfirmBtn; - @FindBy(css = ".t-select-all input") - private WebElement selectAllCheckbox; + @FindBy(id = "projectsSelect") + private WebElement projectsSelectInput; - @FindBy(className = "t-merge-modal") - private WebElement mergeModal; + @FindBy(id = "confirm-copy-samples") + private WebElement copyOkBtn; - //----- OLD BELOW + @FindBy(css = "a.select2-choice") + private WebElement select2Opener; - @FindBy(className = "t-associated-btn") - private WebElement associatedProjectMenuBtn; + @FindBy(className = "select2-search__field") + private WebElement select2Input; - @FindBy(className = "t-associated-dropdown") - private WebElement associatedDropdown; + @FindBy(className = "select2-results__options") + private WebElement select2Results; - @FindBy(className = "t-associated-cb") - private List associatedCbs; + @FindBy(className = "t-filters-btn") + private WebElement filterByPropertyBtn; - @FindBy(className = "selected-counts") - private WebElement selectedCountInfo; + @FindBy(className = "t-apply-filter-btn") + private WebElement applyFiltersBtn; - @FindBy(id = "samplesTable_info") - private WebElement samplesTableInfo; + @FindBy(className = "filter-modal") + private WebElement filterModal; - @FindBy(css = "tbody tr") - private List tableRows; + @FindBy(className = "t-clear-filters") + private WebElement clearFilterBtn; - @FindBy(id = "giveOwner") - private WebElement giveOwnerBtn; + // This will be 'Previous', 1, 2, ..., 'Next' + @FindBy(css = ".pagination li") + private List pagination; - @FindBy(className = "t-move-btn") - private WebElement moveBtn; + // Samples filter date range picker + @FindBy(className = "t-daterange-filter") + private WebElement dateRangeInput; - @FindBy(className = "t-add-cart-btn") - private WebElement addToCartBtn; + @FindBy(name = "daterangepicker_start") + private WebElement daterangepickerStart; - @FindBy(className = "t-remove-modal") - private WebElement removeModal; + @FindBy(name = "daterangepicker_end") + private WebElement daterangepickerEnd; - @FindBy(id = "confirmMergeBtn") - private WebElement mergeBtnOK; + @FindBy(css = "div.ranges li") + private List dateRanges; - @FindBy(id = "sampleName") - private WebElement newMergeNameInput; + @FindBy(className = "applyBtn") + private WebElement applyDateRangeBtn; - @FindBy(className = "t-copy-samples-modal") - private WebElement copySamplesModal; + @FindBy(id = "selection-main") + private WebElement selectionMain; - @FindBy(id = "js-confirm") - private WebElement copyModalConfirmBtn; + @FindBy(id = "selection-toggle") + private WebElement selectionToggle; - @FindBy(id = "projectsSelect") - private WebElement projectsSelectInput; + @FindBy(className = "dt-select-all") + private WebElement selectionAll; - @FindBy(id = "confirm-copy-samples") - private WebElement copyOkBtn; + @FindBy(className = "dt-select-none") + private WebElement selectionNone; - @FindBy(css = "a.select2-choice") - private WebElement select2Opener; + @FindBy(id = "linkerCloseBtn") + private WebElement linkerCloseBtn; - @FindBy(className = "select2-search__field") - private WebElement select2Input; + @FindBy(className = "locked-sample") + private List lockedSamples; - @FindBy(className = "select2-results__options") - private WebElement select2Results; + @FindBy(css = "[data-dt-idx=\"1\"]") + private WebElement firstTablePageBtn; - @FindBy(className = "t-filters-btn") - private WebElement filterByPropertyBtn; + @FindBy(css = ".paginate_button.next a") + private WebElement nextTablePageBtn; - @FindBy(className = "t-apply-filter-btn") - private WebElement applyFiltersBtn; + @FindBy(id = "name") + private WebElement sampleNameInput; - @FindBy(className = "filter-modal") - private WebElement filterModal; + @FindBy(css = "button.ant-btn.ant-btn-primary") + private WebElement okButton; - @FindBy(className = "t-clear-filters") - private WebElement clearFilterBtn; + @FindBy(className = "t-filter-by-file-btn") + private WebElement filterByFileBtn; - // This will be 'Previous', 1, 2, ..., 'Next' - @FindBy(css = ".pagination li") - private List pagination; + @FindBy(className = "t-filter-by-file-input") + private WebElement filterByFileInput; - // Samples filter date range picker - @FindBy(className = "t-daterange-filter") - private WebElement dateRangeInput; + @FindBy(className = "t-filter-submit") + private WebElement filterSubmitBtn; - @FindBy(name = "daterangepicker_start") - private WebElement daterangepickerStart; + @FindBy(className = "t-filter-cancel") + private WebElement filterCancelBtn; - @FindBy(name = "daterangepicker_end") - private WebElement daterangepickerEnd; + @FindBy(className = "ant-pagination-prev") + private WebElement prevTablePage; - @FindBy(css = "div.ranges li") - private List dateRanges; + @FindBy(className = "ant-pagination-next") + private WebElement nextTablePage; - @FindBy(className = "applyBtn") - private WebElement applyDateRangeBtn; + public ProjectSamplesPage(WebDriver driver) { + super(driver); + } + + public static ProjectSamplesPage initPage(WebDriver driver) { + return PageFactory.initElements(driver, ProjectSamplesPage.class); + } + + public static ProjectSamplesPage goToPage(WebDriver driver, Long projectId) { + get(driver, RELATIVE_URL + projectId); + // Wait for full page to get loaded + waitForTime(800); + return PageFactory.initElements(driver, ProjectSamplesPage.class); + } + + public String getTitle() { + return pageHeader.getText(); + } + + public boolean isSampleToolsAvailable() { + try { + return toolsDropdownBtn.isDisplayed(); + } catch (Exception e) { + return false; + } + } + + public void openToolsDropDown() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); + toolsDropdownBtn.click(); + wait.until(ExpectedConditions.visibilityOf(toolsDropdown)); + } + + public void closeToolsDropdown() { + closeDropdown(toolsDropdown); + } - @FindBy(id = "selection-main") - private WebElement selectionMain; + public boolean isMergeBtnEnabled() { + return isElementEnabled(mergeBtn); + } - @FindBy(id = "selection-toggle") - private WebElement selectionToggle; + public boolean isMergeBtnVisible() { + try { + return mergeBtn.isDisplayed(); + } catch (Exception e) { + return false; + } + } - @FindBy(className = "dt-select-all") - private WebElement selectionAll; + public boolean isShareBtnEnabled() { + return isElementEnabled(shareBtn); + } - @FindBy(className = "dt-select-none") - private WebElement selectionNone; + public boolean isRemoveBtnEnabled() { + return isElementEnabled(removeBtn); + } + + public void openExportDropdown() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); + exportSamplesDropdownBtn.click(); + wait.until(ExpectedConditions.visibilityOf(exportDropdown)); + } + + public void closeExportDropdown() { + closeDropdown(exportDropdown); + } + + private boolean isElementEnabled(WebElement element) { + try { + return !element.getAttribute("aria-disabled").equals("true"); + } catch (Exception e) { + // If it does not have "aria-disabled" then it is enabled; + return true; + } + } + + private void closeDropdown(WebElement dropdown) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10L)); + dropdown.sendKeys(Keys.ESCAPE); + wait.until(ExpectedConditions.invisibilityOf(dropdown)); + } + + public boolean isDownloadBtnEnabled() { + return isElementEnabled(downloadBtn); + } + + public boolean isLinkerBtnEnabled() { + return isElementEnabled(linkerBtn); + } + + public boolean isNcbiBtnEnabled() { + return isElementEnabled(ncbiExportBtn); + } - @FindBy(id = "linkerCloseBtn") - private WebElement linkerCloseBtn; + public TableSummary getTableSummary() { + return new TableSummary(tableSummary.getText()); + } - @FindBy(className = "locked-sample") - private List lockedSamples; + public int getNumberProjectsDisplayed() { + return tableRows.size(); + } - @FindBy(css = "[data-dt-idx=\"1\"]") - private WebElement firstTablePageBtn; + public void openCreateNewSampleModal() { + openToolsDropDown(); + createSampleButton.click(); + } - @FindBy(css = ".paginate_button.next a") - private WebElement nextTablePageBtn; + public void filterBySampleName(String name) { + int prevTotal = getTableSummary().getTotal(); + sampleNameFilterToggle.click(); + nameFilterInput.sendKeys(name); + nameFilterInput.sendKeys(Keys.ENTER); + nameFilterInput.sendKeys(Keys.TAB); + waitForTableToUpdate(prevTotal); + } - @FindBy(id = "name") - private WebElement sampleNameInput; + public void clearIndividualSampleNameFilter(String name) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3)); + int prevTotal = getTableSummary().getTotal(); + sampleNameFilterToggle.click(); + WebElement filter = wait.until( + ExpectedConditions.elementToBeClickable(By.cssSelector("[title=\"" + name + "\"]"))); + filter.findElement(By.className("ant-select-selection-item-remove")).click(); + sampleNameFilterToggle.sendKeys(Keys.TAB); + waitForTableToUpdate(prevTotal); + } - @FindBy(className = "t-filter-by-file-btn") - private WebElement filterByFileBtn; + public void filterByOrganism(String organism) { + int prevTotal = getTableSummary().getTotal(); + organismFilterToggle.click(); + organismSelectInput.sendKeys(organism); + organismSelectInput.sendKeys(Keys.ENTER); + organismFilterToggle.sendKeys(Keys.TAB); + waitForTableToUpdate(prevTotal); + } - @FindBy(className = "t-filter-by-file-input") - private WebElement filterByFileInput; + public void clearIndividualOrganismFilter(String organism) { + int prevTotal = getTableSummary().getTotal(); + organismFilterToggle.click(); + WebElement filter = organismFilterSelectedOptions.findElement(By.cssSelector("[title=\"" + organism + "\"]")); + filter.findElement(By.className("ant-select-selection-item-remove")).click(); + organismFilterToggle.sendKeys(Keys.TAB); + waitForTableToUpdate(prevTotal); + } - @FindBy(className = "t-filter-submit") - private WebElement filterSubmitBtn; + public void toggleAssociatedProject(String projectName) { + int prevTotal = getTableSummary().getTotal(); + projectsFilterToggle.click(); + WebElement selection = driver.findElement( + By.xpath("//li[@class='ant-dropdown-menu-item' and span='" + projectName + "']")); + selection.click(); + driver.findElement(By.xpath("//button[@type='button' and span='OK']")).click(); + waitForTableToUpdate(prevTotal); + } - @FindBy(className = "t-filter-cancel") - private WebElement filterCancelBtn; + public void removeAssociatedProject(String projectName) { + projectsFilterToggle.click(); + WebElement selection = driver.findElement(By.xpath("//li/span/span[text()='" + projectName + "']")); + selection.click(); + driver.findElement(By.xpath("//button[@type='button' and span='OK']")).click(); + waitForTime(200); + } - @FindBy(className = "ant-pagination-prev") - private WebElement prevTablePage; + public void filterByCreatedDate(String start, String end) { + int prevTotal = getTableSummary().getTotal(); + createdDateFilterToggle.click(); + driver.findElement(By.xpath("//div[@class='t-created-filter']//input[@placeholder='Start date']")) + .sendKeys(start); + WebElement endInput = driver.findElement( + By.xpath("//div[@class='t-created-filter']//input[@placeholder='End date']")); + endInput.sendKeys(end); + endInput.sendKeys(Keys.ENTER); + createdDateFilter.findElement(By.className("t-search-btn")).click(); + waitForTableToUpdate(prevTotal); + } - @FindBy(className = "ant-pagination-next") - private WebElement nextTablePage; + public void clearFilterByCreatedDate() { + int prevTotal = getTableSummary().getTotal(); + createdDateFilterToggle.click(); + createdDateFilter.findElement(By.className("t-clear-btn")).click(); + createdDateFilterToggle.click(); + waitForTableToUpdate(prevTotal); + } - public ProjectSamplesPage(WebDriver driver) { - super(driver); - } + public void filterByModifiedDate(String start, String end) { + int prevTotal = getTableSummary().getTotal(); + modifiedDateFilterToggle.click(); + driver.findElement(By.xpath("//div[@class='t-modified-filter']//input[@placeholder='Start date']")) + .sendKeys(start); + WebElement endInput = driver.findElement( + By.xpath("//div[@class='t-modified-filter']//input[@placeholder='End date']")); + endInput.sendKeys(end); + endInput.sendKeys(Keys.ENTER); + modifiedDateFilter.findElement(By.className("t-search-btn")).click(); + waitForTableToUpdate(prevTotal); + } - public static ProjectSamplesPage initPage(WebDriver driver) { - return PageFactory.initElements(driver, ProjectSamplesPage.class); - } + public void clearFilterByModifiedDate() { + int prevTotal = getTableSummary().getTotal(); + modifiedDateFilterToggle.click(); + modifiedDateFilter.findElement(By.className("t-clear-btn")).click(); + modifiedDateFilterToggle.click(); + waitForTableToUpdate(prevTotal); + } - public static ProjectSamplesPage goToPage(WebDriver driver, int projectId) { - get(driver, RELATIVE_URL + projectId); - // Wait for full page to get loaded - waitForTime(800); - return PageFactory.initElements(driver, ProjectSamplesPage.class); - } - - public String getTitle() { - return pageHeader.getText(); - } - - public boolean isSampleToolsAvailable() { - try { - return toolsDropdownBtn.isDisplayed(); - } catch (Exception e) { - return false; - } - } - - public void openToolsDropDown() { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); - toolsDropdownBtn.click(); - wait.until(ExpectedConditions.visibilityOf(toolsDropdown)); - } - - public void closeToolsDropdown() { - closeDropdown(toolsDropdown); - } - - public boolean isMergeBtnEnabled() { - return isElementEnabled(mergeBtn); - } - - public boolean isMergeBtnVisible() { - try { - return mergeBtn.isDisplayed(); - } catch (Exception e) { - return false; - } - } - - public boolean isShareBtnEnabled() { - return isElementEnabled(shareBtn); - } - - public boolean isRemoveBtnEnabled() { - return isElementEnabled(removeBtn); - } - - public void openExportDropdown() { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); - exportSamplesDropdownBtn.click(); - wait.until(ExpectedConditions.visibilityOf(exportDropdown)); - } - - public void closeExportDropdown() { - closeDropdown(exportDropdown); - } - - private boolean isElementEnabled(WebElement element) { - try { - return !element.getAttribute("aria-disabled").equals("true"); - } catch (Exception e) { - // If it does not have "aria-disabled" then it is enabled; - return true; - } - } - - private void closeDropdown(WebElement dropdown) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10L)); - dropdown.sendKeys(Keys.ESCAPE); - wait.until(ExpectedConditions.invisibilityOf(dropdown)); - } - - public boolean isDownloadBtnEnabled() { - return isElementEnabled(downloadBtn); - } - - public boolean isLinkerBtnEnabled() { - return isElementEnabled(linkerBtn); - } - - public boolean isNcbiBtnEnabled() { - return isElementEnabled(ncbiExportBtn); - } - - public TableSummary getTableSummary() { - return new TableSummary(tableSummary.getText()); - } - - public int getNumberProjectsDisplayed() { - return tableRows.size(); - } - - public void openCreateNewSampleModal() { - openToolsDropDown(); - createSampleButton.click(); - } - - public void filterBySampleName(String name) { - int prevTotal = getTableSummary().getTotal(); - sampleNameFilterToggle.click(); - nameFilterInput.sendKeys(name); - nameFilterInput.sendKeys(Keys.ENTER); - nameFilterInput.sendKeys(Keys.TAB); - waitForTableToUpdate(prevTotal); - } - - public void clearIndividualSampleNameFilter(String name) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3)); - int prevTotal = getTableSummary().getTotal(); - sampleNameFilterToggle.click(); - WebElement filter = wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("[title=\"" + name + "\"]"))); - filter.findElement(By.className("ant-select-selection-item-remove")).click(); - sampleNameFilterToggle.sendKeys(Keys.TAB); - waitForTableToUpdate(prevTotal); - } - - public void filterByOrganism(String organism) { - int prevTotal = getTableSummary().getTotal(); - organismFilterToggle.click(); - organismSelectInput.sendKeys(organism); - organismSelectInput.sendKeys(Keys.ENTER); - organismFilterToggle.sendKeys(Keys.TAB); - waitForTableToUpdate(prevTotal); - } - - public void clearIndividualOrganismFilter(String organism) { - int prevTotal = getTableSummary().getTotal(); - organismFilterToggle.click(); - WebElement filter = organismFilterSelectedOptions.findElement(By.cssSelector("[title=\"" + organism + "\"]")); - filter.findElement(By.className("ant-select-selection-item-remove")).click(); - organismFilterToggle.sendKeys(Keys.TAB); - waitForTableToUpdate(prevTotal); - } - - public void toggleAssociatedProject(String projectName) { - int prevTotal = getTableSummary().getTotal(); - projectsFilterToggle.click(); - WebElement selection = driver - .findElement(By.xpath("//li[@class='ant-dropdown-menu-item' and span='" + projectName + "']")); - selection.click(); - driver.findElement(By.xpath("//button[@type='button' and span='OK']")).click(); - waitForTableToUpdate(prevTotal); - } - - public void removeAssociatedProject(String projectName) { - projectsFilterToggle.click(); - WebElement selection = driver.findElement(By.xpath("//li/span/span[text()='" + projectName + "']")); - selection.click(); - driver.findElement(By.xpath("//button[@type='button' and span='OK']")).click(); - waitForTime(200); - } - - public void filterByCreatedDate(String start, String end) { - int prevTotal = getTableSummary().getTotal(); - createdDateFilterToggle.click(); - driver.findElement(By.xpath("//div[@class='t-created-filter']//input[@placeholder='Start date']")) - .sendKeys(start); - WebElement endInput = driver - .findElement(By.xpath("//div[@class='t-created-filter']//input[@placeholder='End date']")); - endInput.sendKeys(end); - endInput.sendKeys(Keys.ENTER); - createdDateFilter.findElement(By.className("t-search-btn")).click(); - waitForTableToUpdate(prevTotal); - } - - public void clearFilterByCreatedDate() { - int prevTotal = getTableSummary().getTotal(); - createdDateFilterToggle.click(); - createdDateFilter.findElement(By.className("t-clear-btn")).click(); - createdDateFilterToggle.click(); - waitForTableToUpdate(prevTotal); - } - - public void filterByModifiedDate(String start, String end) { - int prevTotal = getTableSummary().getTotal(); - modifiedDateFilterToggle.click(); - driver.findElement(By.xpath("//div[@class='t-modified-filter']//input[@placeholder='Start date']")) - .sendKeys(start); - WebElement endInput = driver - .findElement(By.xpath("//div[@class='t-modified-filter']//input[@placeholder='End date']")); - endInput.sendKeys(end); - endInput.sendKeys(Keys.ENTER); - modifiedDateFilter.findElement(By.className("t-search-btn")).click(); - waitForTableToUpdate(prevTotal); - } - - public void clearFilterByModifiedDate() { - int prevTotal = getTableSummary().getTotal(); - modifiedDateFilterToggle.click(); - modifiedDateFilter.findElement(By.className("t-clear-btn")).click(); - modifiedDateFilterToggle.click(); - waitForTableToUpdate(prevTotal); - } - - public void selectSampleByName(String sampleName) { - WebElement checkbox = samplesTable.findElement(By.xpath("//td/button[span[text()='" + sampleName + "']]/../..//input")); - checkbox.click(); - } + public void selectSampleByName(String sampleName) { + WebElement checkbox = samplesTable.findElement( + By.xpath("//td/button[span[text()='" + sampleName + "']]/../..//input")); + checkbox.click(); + } public void clickSampleName(String sampleName) { - WebElement sampleNameLink = samplesTable.findElement(By.xpath("//td/button[span[text()='" + sampleName + "']]")); + WebElement sampleNameLink = samplesTable.findElement( + By.xpath("//td/button[span[text()='" + sampleName + "']]")); sampleNameLink.click(); } @@ -467,161 +473,166 @@ public Long getCoverageForSampleByName(String sampleName) { By.xpath("//td/button[span[text()='" + sampleName + "']]/../../td[contains(@class, 't-td-coverage')]")); String coverageString = coverageCell.getText(); - return coverageString == null || coverageString.isEmpty() ? null : Long.parseLong(coverageString); - } - - public void addSelectedSamplesToCart() { - addToCartBtn.click(); - // Make sure the item were added to the cart. - waitForElementVisible(By.className("t-cart-count")); - // If the cart count is already visible this can go too fast, - // wait for the cart to fully update it's total. - waitForTime(500); - } - - public String getLinkerCommand() { - openLinkerModal(); - String command = linkerCmd.getText(); - closeLinkerModal(); - return command; - } - - public String getLinkerCommandWithAssembly() { - openLinkerModal(); - WebElement fileTypeCheckbox = linkerModal.findElement(By.xpath("//input[@value='assembly']")); - boolean isChecked = fileTypeCheckbox.isSelected(); - fileTypeCheckbox.click(); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - wait.until(ExpectedConditions.elementSelectionStateToBe(fileTypeCheckbox, !isChecked)); - String command = linkerCmd.getText(); - closeLinkerModal(); - return command; - - } - - private void openLinkerModal() { - openExportDropdown(); - linkerBtn.click(); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); - wait.until(ExpectedConditions.visibilityOf(linkerModal)); - } - - private void closeLinkerModal() { - driver.findElement(By.xpath("//button[@type='button' and span='Close']")).click(); - } - - public void toggleSelectAll() { - boolean checked = selectAllCheckbox.isSelected(); - selectAllCheckbox.click(); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - wait.until(ExpectedConditions.elementSelectionStateToBe(selectAllCheckbox, !checked)); - } - - public void mergeSamplesWithOriginalName(String sampleName) { - toolsDropdownBtn.click(); - mergeBtn.click(); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - wait.until(ExpectedConditions.visibilityOf(mergeModal)); - WebElement existing = null; - try { - mergeModal.findElement(By.xpath("//label[(.//*|.)[contains(text(), '" + sampleName + "')]]")).click(); - } catch (Exception e) { - driver.findElement(By.className("t-custom-checkbox")).click(); - driver.findElement(By.id("newName")).sendKeys(sampleName); - } - driver.findElement(By.xpath("//button[@type='button' and span='Merge Samples']")).click(); - wait.until(ExpectedConditions.textMatches(By.className("t-summary"), Pattern.compile("^Selected: 0"))); - } - - public String getMostRecentlyModifiedSampleName() { - WebElement nameAnchor = driver.findElement(By.xpath("//tbody/tr[1]/td[2]/button")); - return nameAnchor.getText(); - } - - public void removeSamples() { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - openToolsDropDown(); - removeBtn.click(); - wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.className("t-remove-modal"))); - removeModal.findElement(By.xpath("//button[@type='button' and span='Remove Samples']")).click(); - wait.until(ExpectedConditions.textMatches(By.className("t-summary"), Pattern.compile("^Selected: 0"))); - } - - public void shareSamples() { - WebDriverWait wait = openToolsDropdownAndWait(); - wait.until(ExpectedConditions.visibilityOf(shareBtn)); - shareBtn.click(); - } - - private WebDriverWait openToolsDropdownAndWait() { - toolsDropdownBtn.click(); - return new WebDriverWait(driver, Duration.ofSeconds(10)); - } - - public void enterSampleName(String sampleName) { - sampleNameInput.sendKeys(Keys.CONTROL + "a", Keys.DELETE); - sampleNameInput.sendKeys(sampleName); - waitForTime(1000); - } - - public boolean isSampleNameErrorDisplayed() { - return driver.findElements(By.cssSelector(".t-sample-name-wrapper .ant-form-item-explain-error")).size() > 0; - } - - private void waitForTableToUpdate(int prevTotal) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); - wait.until(ExpectedConditions.presenceOfElementLocated( - By.xpath("//td[contains(@class, 't-summary') and not(text()='Selected: 0 of " + prevTotal + "')]"))); - } - - public void filterByFile(String file1) { - filterByFileBtn.click(); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - wait.until(ExpectedConditions.visibilityOf(filterByFileInput)); - Path path = Paths.get(file1); - filterByFileInput.sendKeys(path.toAbsolutePath().toString()); - waitForTime(200); - } - - public List getInvalidSampleNames() { - List invalidSampleNames = new ArrayList<>(); - List invalidSampleNamesElements = driver.findElements(By.cssSelector(".t-invalid-sample")); - for (WebElement invalidSampleNameElement : invalidSampleNamesElements) { - invalidSampleNames.add(invalidSampleNameElement.getText()); - } - return invalidSampleNames; - } - - public void cancelFilterByFile() { - filterCancelBtn.click(); - } - - public void submitFilterByFile() { - int total = getTableSummary().getTotal(); - filterSubmitBtn.click(); - waitForTableToUpdate(total); - } - - public void shareExportSamplesToNcbi() { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); - openExportDropdown(); - ncbiExportBtn.click(); - wait.until(ExpectedConditions.urlContains("/ncbi")); - } - - public void goToNextTablePage() { - nextTablePage.click(); - waitForTime(200); - } - - public void gotToPreviousTablePage() { - prevTablePage.click(); - waitForTime(200); - } - - public boolean isMessageDisplayed(String message) { - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(4)); - WebElement notification = wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("ant-notification-notice-message"))); - return wait.until(ExpectedConditions.textToBePresentInElement(notification, message)); - } + return coverageString == null || coverageString.isEmpty() ? null : Long.parseLong(coverageString); + } + + public void addSelectedSamplesToCart() { + addToCartBtn.click(); + // Make sure the item were added to the cart. + waitForElementVisible(By.className("t-cart-count")); + // If the cart count is already visible this can go too fast, + // wait for the cart to fully update it's total. + waitForTime(500); + } + + public String getLinkerCommand() { + openLinkerModal(); + String command = linkerCmd.getText(); + closeLinkerModal(); + return command; + } + + public String getLinkerCommandWithAssembly() { + openLinkerModal(); + WebElement fileTypeCheckbox = linkerModal.findElement(By.xpath("//input[@value='assembly']")); + boolean isChecked = fileTypeCheckbox.isSelected(); + fileTypeCheckbox.click(); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + wait.until(ExpectedConditions.elementSelectionStateToBe(fileTypeCheckbox, !isChecked)); + String command = linkerCmd.getText(); + closeLinkerModal(); + return command; + + } + + private void openLinkerModal() { + openExportDropdown(); + linkerBtn.click(); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); + wait.until(ExpectedConditions.visibilityOf(linkerModal)); + } + + private void closeLinkerModal() { + driver.findElement(By.xpath("//button[@type='button' and span='Close']")).click(); + } + + public void toggleSelectAll() { + boolean checked = selectAllCheckbox.isSelected(); + selectAllCheckbox.click(); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + wait.until(ExpectedConditions.elementSelectionStateToBe(selectAllCheckbox, !checked)); + } + + public void mergeSamplesWithOriginalName(String sampleName) { + toolsDropdownBtn.click(); + mergeBtn.click(); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + wait.until(ExpectedConditions.visibilityOf(mergeModal)); + WebElement existing = null; + try { + mergeModal.findElement(By.xpath("//label[(.//*|.)[contains(text(), '" + sampleName + "')]]")).click(); + } catch (Exception e) { + driver.findElement(By.className("t-custom-checkbox")).click(); + driver.findElement(By.id("newName")).sendKeys(sampleName); + } + driver.findElement(By.xpath("//button[@type='button' and span='Merge Samples']")).click(); + wait.until(ExpectedConditions.textMatches(By.className("t-summary"), Pattern.compile("^Selected: 0"))); + } + + public String getMostRecentlyModifiedSampleName() { + WebElement nameAnchor = driver.findElement(By.xpath("//tbody/tr[1]/td[2]/button")); + return nameAnchor.getText(); + } + + public void removeSamples() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + openToolsDropDown(); + removeBtn.click(); + wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.className("t-remove-modal"))); + removeModal.findElement(By.xpath("//button[@type='button' and span='Remove Samples']")).click(); + wait.until(ExpectedConditions.textMatches(By.className("t-summary"), Pattern.compile("^Selected: 0"))); + } + + public void shareSamples() { + WebDriverWait wait = openToolsDropdownAndWait(); + wait.until(ExpectedConditions.visibilityOf(shareBtn)); + shareBtn.click(); + } + + private WebDriverWait openToolsDropdownAndWait() { + toolsDropdownBtn.click(); + return new WebDriverWait(driver, Duration.ofSeconds(10)); + } + + public void enterSampleName(String sampleName) { + sampleNameInput.sendKeys(Keys.CONTROL + "a", Keys.DELETE); + sampleNameInput.sendKeys(sampleName); + waitForTime(1000); + } + + public void clickOk() { + okButton.click(); + } + + public boolean isSampleNameErrorDisplayed() { + return driver.findElements(By.cssSelector(".t-sample-name-wrapper .ant-form-item-explain-error")).size() > 0; + } + + private void waitForTableToUpdate(int prevTotal) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); + wait.until(ExpectedConditions.presenceOfElementLocated( + By.xpath("//td[contains(@class, 't-summary') and not(text()='Selected: 0 of " + prevTotal + "')]"))); + } + + public void filterByFile(String file1) { + filterByFileBtn.click(); + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + wait.until(ExpectedConditions.visibilityOf(filterByFileInput)); + Path path = Paths.get(file1); + filterByFileInput.sendKeys(path.toAbsolutePath().toString()); + waitForTime(200); + } + + public List getInvalidSampleNames() { + List invalidSampleNames = new ArrayList<>(); + List invalidSampleNamesElements = driver.findElements(By.cssSelector(".t-invalid-sample")); + for (WebElement invalidSampleNameElement : invalidSampleNamesElements) { + invalidSampleNames.add(invalidSampleNameElement.getText()); + } + return invalidSampleNames; + } + + public void cancelFilterByFile() { + filterCancelBtn.click(); + } + + public void submitFilterByFile() { + int total = getTableSummary().getTotal(); + filterSubmitBtn.click(); + waitForTableToUpdate(total); + } + + public void shareExportSamplesToNcbi() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2)); + openExportDropdown(); + ncbiExportBtn.click(); + wait.until(ExpectedConditions.urlContains("/ncbi")); + } + + public void goToNextTablePage() { + nextTablePage.click(); + waitForTime(200); + } + + public void gotToPreviousTablePage() { + prevTablePage.click(); + waitForTime(200); + } + + public boolean isMessageDisplayed(String message) { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(4)); + WebElement notification = wait.until( + ExpectedConditions.visibilityOfElementLocated(By.className("ant-notification-notice-message"))); + return wait.until(ExpectedConditions.textToBePresentInElement(notification, message)); + } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/AssemblyPipelinePageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/AssemblyPipelinePageIT.java index 0307561fd76..e94eea17e33 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/AssemblyPipelinePageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/AssemblyPipelinePageIT.java @@ -33,7 +33,7 @@ public void setUpTest() throws IOException { private void addSamplesToCart() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample1"); samplesPage.selectSampleByName("sample2"); samplesPage.addSelectedSamplesToCart(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/BioHanselPipelinePageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/BioHanselPipelinePageIT.java index 03e07e4d538..0c7f810cde1 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/BioHanselPipelinePageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/BioHanselPipelinePageIT.java @@ -35,7 +35,7 @@ public void setUpTest() throws IOException { private void addSamplesToCart() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample1"); samplesPage.selectSampleByName("sample2"); samplesPage.addSelectedSamplesToCart(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/PipelinesPhylogenomicsPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/PipelinesPhylogenomicsPageIT.java index f90b54d1205..04164095c81 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/PipelinesPhylogenomicsPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/pipelines/PipelinesPhylogenomicsPageIT.java @@ -35,7 +35,7 @@ public void setUpTest() throws IOException { private void addSamplesToCart() { LoginPage.loginAsUser(driver()); - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample1"); samplesPage.selectSampleByName("sample2"); samplesPage.addSelectedSamplesToCart(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/CreateProjectIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/CreateProjectIT.java index a274769368d..2942a200d5d 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/CreateProjectIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/CreateProjectIT.java @@ -92,7 +92,7 @@ public void testCreateProjectWithSamples() { String name = "TESTING PROJECT NAME"; // Add some samples - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample"); samplesPage.addSelectedSamplesToCart(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/NcbiExportPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/NcbiExportPageIT.java index d3408724ac4..8050d92c8d1 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/NcbiExportPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/NcbiExportPageIT.java @@ -27,7 +27,7 @@ void testCreateNcbiSubmission() throws Exception { String DEFAULT_PROTOCOL = "DEFAULT_PROTOCOL"; LoginPage.loginAsManager(driver()); - int PROJECT_ID = 1; + Long PROJECT_ID = 1L; ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), PROJECT_ID); samplesPage.selectSampleByName(SAMPLE_1); samplesPage.selectSampleByName(SAMPLE_2); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectMembersPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectMembersPageIT.java index 25444e22c2a..15f566554a1 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectMembersPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectMembersPageIT.java @@ -1,5 +1,7 @@ package ca.corefacility.bioinformatics.irida.ria.integration.projects; +import org.junit.jupiter.api.Test; + import ca.corefacility.bioinformatics.irida.model.enums.ProjectMetadataRole; import ca.corefacility.bioinformatics.irida.model.enums.ProjectRole; import ca.corefacility.bioinformatics.irida.ria.integration.AbstractIridaUIITChromeDriver; @@ -9,8 +11,9 @@ import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectDetailsPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSyncPage; import ca.corefacility.bioinformatics.irida.ria.integration.utilities.RemoteApiUtilities; + import com.github.springtestdbunit.annotation.DatabaseSetup; -import org.junit.jupiter.api.Test; +import com.github.springtestdbunit.annotation.DatabaseTearDown; import static org.junit.jupiter.api.Assertions.*; @@ -20,6 +23,7 @@ *

*/ @DatabaseSetup("/ca/corefacility/bioinformatics/irida/ria/web/ProjectsPageIT.xml") +@DatabaseTearDown("/ca/corefacility/bioinformatics/irida/test/integration/TableReset.xml") public class ProjectMembersPageIT extends AbstractIridaUIITChromeDriver { @Test @@ -40,7 +44,7 @@ public void testCanManagePageSetUp() { assertEquals(2, page.getNumberOfMembers(), "Should be 2 member in the project"); // Test Add user to project - page.addUserToProject("test"); + page.addUserToProject("test", ProjectRole.PROJECT_USER.toString()); page.isNotificationDisplayed(); assertEquals(3, page.getNumberOfMembers(), "Should be 3 members in the project"); @@ -99,9 +103,8 @@ public void testRemoteProjectManagerPageSetup() { ProjectMembersPage remoteProjectMembersPage = ProjectMembersPage.goToRemoteProject(driver(), projectId); assertEquals(1, remoteProjectMembersPage.getNumberOfMembers(), "Should be 1 members in the project"); - remoteProjectMembersPage.addUserToProject("Mr. Manager"); + remoteProjectMembersPage.addUserToProject("Mr. Manager", ProjectRole.PROJECT_OWNER.toString()); assertTrue(remoteProjectMembersPage.isNotificationDisplayed()); - remoteProjectMembersPage.updateUserRole(0, ProjectRole.PROJECT_OWNER.toString()); assertEquals(2, remoteProjectMembersPage.getNumberOfMembers(), "Should be 2 members in the project"); LoginPage.loginAsManager(driver()); @@ -113,7 +116,7 @@ public void testRemoteProjectManagerPageSetup() { ProjectMembersPage managerRemoteProjectMembersPage = ProjectMembersPage.goToRemoteProject(driver(), projectId); assertTrue(managerRemoteProjectMembersPage.isAddMemberBtnVisible(), "Add member button should be visible"); - managerRemoteProjectMembersPage.addUserToProject("testUser"); + managerRemoteProjectMembersPage.addUserToProject("testUser", ProjectRole.PROJECT_USER.toString()); assertTrue(remoteProjectMembersPage.isNotificationDisplayed()); assertEquals(3, remoteProjectMembersPage.getNumberOfMembers(), "Should be 3 members in the project"); managerRemoteProjectMembersPage.removeUser(0); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSampleMetadataImportPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSampleMetadataImportPageIT.java index 88ce9a34c70..51e07294c3a 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSampleMetadataImportPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSampleMetadataImportPageIT.java @@ -10,7 +10,10 @@ import ca.corefacility.bioinformatics.irida.ria.integration.AbstractIridaUIITChromeDriver; import ca.corefacility.bioinformatics.irida.ria.integration.pages.LoginPage; +import ca.corefacility.bioinformatics.irida.ria.integration.pages.ProjectMembersPage; +import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectDeletePage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSampleMetadataImportPage; +import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSamplesPage; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.google.common.collect.ImmutableList; @@ -24,10 +27,12 @@ public class ProjectSampleMetadataImportPageIT extends AbstractIridaUIITChromeDr private static final String MIXED_FILE_PATH = "src/test/resources/files/metadata-upload/mixed.xlsx"; private static final String INVALID_FILE_PATH = "src/test/resources/files/metadata-upload/invalid.xlsx"; private static final String SAMPLE_NAME_COLUMN = "NLEP #"; + private static final Long PROJECT_ID = 1L; @BeforeEach public void init() { LoginPage.loginAsManager(driver()); + LoginPage.loginAsAdmin(driver2()); } @Test @@ -36,8 +41,8 @@ public void testGoodFileAndHeaders() { page.uploadMetadataFile(GOOD_FILE_PATH); page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); page.goToReviewPage(); - assertEquals(5, page.getUpdateCount(), "Has incorrect amount of update sample rows"); - assertEquals(0, page.getNewCount(), "Has incorrect amount of new sample rows"); + assertEquals(4, page.getUpdateCount(), "Has incorrect amount of update sample rows"); + assertEquals(1, page.getNewCount(), "Has incorrect amount of new sample rows"); /* Check formatting. A special check for number column formatting has been added in July 2020. @@ -55,7 +60,6 @@ public void testGoodFileAndHeaders() { List formattedNumbers = page.getValuesForColumnByName("Numbers"); formattedNumbers.forEach(num -> assertTrue(values.contains(Double.valueOf(num)), "Found " + num + " that was not formatted properly")); - } @Test @@ -64,8 +68,8 @@ public void testMixedFileAndHeaders() { page.uploadMetadataFile(MIXED_FILE_PATH); page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); page.goToReviewPage(); - assertEquals(5, page.getUpdateCount(), "Has incorrect amount of update sample rows"); - assertEquals(2, page.getNewCount(), "Has incorrect amount of new sample rows"); + assertEquals(4, page.getUpdateCount(), "Has incorrect amount of update sample rows"); + assertEquals(3, page.getNewCount(), "Has incorrect amount of new sample rows"); } @Test @@ -89,4 +93,76 @@ public void testFailedUpload() { page.goToReviewPage(); assertTrue(page.isAlertDisplayed(), "Validation message did not display"); } + + @Test + public void testFailedUploadByDeletingProject() { + //manager starts a metadata import + ProjectSampleMetadataImportPage page = ProjectSampleMetadataImportPage.goToPage(driver()); + page.uploadMetadataFile(GOOD_FILE_PATH); + page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); + page.goToReviewPage(); + + //admin deletes project + ProjectDeletePage deleteProjectPage = ProjectDeletePage.goTo(driver2(), PROJECT_ID); + deleteProjectPage.clickConfirm(); + deleteProjectPage.deleteProject(); + + //manager tries to complete metadata import + page.clickUploadButton(); + assertTrue(page.isErrorNotificationDisplayed(), "Error notification did not display"); + } + + @Test + public void testFailedUploadByDeletingSamples() { + //manager starts a metadata import + ProjectSampleMetadataImportPage page = ProjectSampleMetadataImportPage.goToPage(driver()); + page.uploadMetadataFile(GOOD_FILE_PATH); + page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); + page.goToReviewPage(); + + //admin deletes samples + ProjectSamplesPage projectSamplesPage = ProjectSamplesPage.goToPage(driver2(), PROJECT_ID); + projectSamplesPage.toggleSelectAll(); + projectSamplesPage.removeSamples(); + + //manager tries to complete metadata import + page.clickUploadButton(); + assertTrue(page.hasTableErrors(), "Table errors did not display"); + } + + @Test + public void testFailedUploadByCreatingNewSample() { + //manager starts a metadata import + ProjectSampleMetadataImportPage page = ProjectSampleMetadataImportPage.goToPage(driver()); + page.uploadMetadataFile(GOOD_FILE_PATH); + page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); + page.goToReviewPage(); + + //admin creates new sample + ProjectSamplesPage projectSamplesPage = ProjectSamplesPage.goToPage(driver2(), PROJECT_ID); + projectSamplesPage.openCreateNewSampleModal(); + projectSamplesPage.enterSampleName("sample5"); + projectSamplesPage.clickOk(); + + //manager tries to complete metadata import + page.clickUploadButton(); + assertTrue(page.hasTableErrors(), "Table errors did not display"); + } + + @Test + public void testFailedUploadByRemovingPrivileges() { + //manager starts a metadata import + ProjectSampleMetadataImportPage page = ProjectSampleMetadataImportPage.goToPage(driver()); + page.uploadMetadataFile(GOOD_FILE_PATH); + page.selectSampleNameColumn(SAMPLE_NAME_COLUMN); + page.goToReviewPage(); + + //admin removes manager from project + ProjectMembersPage projectMembersPage = ProjectMembersPage.goTo(driver2()); + projectMembersPage.removeManager(0); + + //manager tries to complete metadata import + page.clickUploadButton(); + assertTrue(page.isErrorNotificationDisplayed(), "Error notification did not display"); + } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSamplesPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSamplesPageIT.java index 19080dde050..f1fef3675af 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSamplesPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectSamplesPageIT.java @@ -1,18 +1,20 @@ package ca.corefacility.bioinformatics.irida.ria.integration.projects; +import java.time.Duration; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + import ca.corefacility.bioinformatics.irida.ria.integration.AbstractIridaUIITChromeDriver; import ca.corefacility.bioinformatics.irida.ria.integration.pages.LoginPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSamplesPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ShareSamplesPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.TableSummary; + import com.github.springtestdbunit.annotation.DatabaseSetup; import com.google.common.collect.ImmutableList; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -import java.time.Duration; -import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -36,14 +38,14 @@ public void testGoingToInvalidPage() { LoginPage.loginAsManager(driver()); assertThrows(AssertionError.class, () -> { - ProjectSamplesPage.goToPage(driver(), 100); + ProjectSamplesPage.goToPage(driver(), 100L); }); } @Test public void testPageSetUp() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); assertEquals("Samples", page.getActivePage(), "Should have the project name as the page main header."); assertEquals(10, page.getNumberProjectsDisplayed(), "Should display 10 projects initially."); @@ -52,14 +54,14 @@ public void testPageSetUp() { @Test public void testToolbarButtonsAsCollaborator() { LoginPage.loginAsUser(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); assertFalse(page.isSampleToolsAvailable(), "Sample Tools should be hidden from a collaborator"); } @Test public void testToolbarButtonsAsManager() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); // Test set up with no sample selected assertTrue(page.isSampleToolsAvailable(), "Sample Tools should be visible for a manager"); @@ -99,7 +101,7 @@ public void testToolbarButtonsAsManager() { @Test public void testSampleSelection() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary summary = page.getTableSummary(); assertEquals(0, summary.getSelected(), "Should be 0 selected samples"); assertEquals(PROJECT_SAMPLES_COUNT, summary.getTotal(), "Should be 0 selected samples"); @@ -126,7 +128,7 @@ public void testSampleSelection() { @Test public void testMergeSamples() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary originalSummary = page.getTableSummary(); String NEW_NAME = "I-AM-NEW-HERE"; @@ -154,7 +156,7 @@ public void testMergeSamples() { public void testRemoteProjectSamplesManagerSetup() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 7); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 7L); page.selectSampleByName("sample23p7"); page.selectSampleByName("sample24p7"); @@ -166,7 +168,7 @@ public void testRemoteProjectSamplesManagerSetup() { @Test public void testRemoveSamplesFromProject() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary summary = page.getTableSummary(); page.selectSampleByName(FIRST_SAMPLE_NAME); @@ -187,7 +189,7 @@ public void testFilteringSamplesByProperties() { String ASSOCIATED_PROJECT_FILTER = "project6"; LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary summary = page.getTableSummary(); assertEquals(PROJECT_SAMPLES_COUNT, summary.getTotal(), "Without the filter there should be 23 elements in the table"); @@ -273,7 +275,7 @@ public void testFilteringSamplesByProperties() { @Test public void testCartFunctionality() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); // Select some samples page.selectSampleByName(FIRST_SAMPLE_NAME); @@ -289,7 +291,7 @@ public void testCartFunctionality() { @Test public void testLinkerFunctionality() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); assertEquals("ngsArchiveLinker.pl -p 1 -t fastq", page.getLinkerCommand(), "Should be the correct linker command"); @@ -312,7 +314,7 @@ public void testLinkerFunctionality() { @Test public void testAddNewSamples() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); page.openCreateNewSampleModal(); page.enterSampleName("BAD"); assertTrue(page.isSampleNameErrorDisplayed(), "Should show a warning message"); @@ -330,7 +332,7 @@ public void testFilterByFile() { int numberValidSampleNames = 5; LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary summary = page.getTableSummary(); assertEquals(PROJECT_SAMPLES_COUNT, summary.getTotal(), @@ -353,7 +355,7 @@ public void testFilterByFileWithAssociatedProjects() { String ASSOCIATED_SAMPLE_NAME = "sample5fg45"; LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); TableSummary summary = page.getTableSummary(); assertEquals(PROJECT_SAMPLES_COUNT, summary.getTotal(), @@ -381,7 +383,7 @@ public void testFilterByFileWindowsEncoding() { List actualInvalidNames = ImmutableList.of("11-0001", "10-1928", "10-8727"); LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); page.filterByFile("src/test/resources/files/filter-by-file/sample-filter-windows.txt"); List invalidSamples = page.getInvalidSampleNames(); invalidSamples.forEach( @@ -393,7 +395,7 @@ public void testCoverageColumnWithProjectCoverageSettings() { String SAMPLE_WITH_COVERAGE_QC_ENTRY = "sample1"; LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); page.filterBySampleName(SAMPLE_WITH_COVERAGE_QC_ENTRY); TableSummary summary = page.getTableSummary(); @@ -409,7 +411,7 @@ public void testCoverageColumnWithoutProjectCoverageSettings() { String SAMPLE_WITH_COVERAGE_QC_ENTRY = "sample5fg44"; LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 6); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 6L); assertNull(page.getCoverageForSampleByName(SAMPLE_WITH_COVERAGE_QC_ENTRY), SAMPLE_WITH_COVERAGE_QC_ENTRY + " should have a value"); @@ -418,7 +420,7 @@ public void testCoverageColumnWithoutProjectCoverageSettings() { @Test void testRemoveLockedSample() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); page.selectSampleByName(FIRST_SAMPLE_NAME); page.openToolsDropDown(); page.shareSamples(); @@ -432,7 +434,7 @@ void testRemoveLockedSample() { shareSamplesPage.gotToNextStep(); shareSamplesPage.submitShareRequest(); - page = ProjectSamplesPage.goToPage(driver(), 2); + page = ProjectSamplesPage.goToPage(driver(), 2L); TableSummary summary = page.getTableSummary(); assertEquals(1, summary.getTotal(), "Should have 1 sample"); page.selectSampleByName(FIRST_SAMPLE_NAME); @@ -445,7 +447,7 @@ void testRemoveLockedSample() { @Test void testSharingWithLockedSamplesAsManager() { LoginPage.loginAsManager(driver()); - ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage page = ProjectSamplesPage.goToPage(driver(), 1L); page.selectSampleByName(LOCKED_SAMPLE_NAME); page.openToolsDropDown(); page.shareSamples(); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectShareSamplesIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectShareSamplesIT.java index 70e5f5e4155..00ec93e03cf 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectShareSamplesIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectShareSamplesIT.java @@ -1,11 +1,13 @@ package ca.corefacility.bioinformatics.irida.ria.integration.projects; +import org.junit.jupiter.api.Test; + import ca.corefacility.bioinformatics.irida.ria.integration.AbstractIridaUIITChromeDriver; import ca.corefacility.bioinformatics.irida.ria.integration.pages.LoginPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ProjectSamplesPage; import ca.corefacility.bioinformatics.irida.ria.integration.pages.projects.ShareSamplesPage; + import com.github.springtestdbunit.annotation.DatabaseSetup; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -20,11 +22,12 @@ public void testShareSamplesAsManager() { LoginPage.loginAsManager(driver()); // SHARING SINGLE SAMPLE - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample5fg44"); samplesPage.shareSamples(); - assertFalse(shareSamplesPage.isNextButtonEnabled(), "The next button should not be enabled when going to the page"); + assertFalse(shareSamplesPage.isNextButtonEnabled(), + "The next button should not be enabled when going to the page"); shareSamplesPage.searchForProject("3"); assertThat(shareSamplesPage.getProjectSelectText()).contains("ID: 3"); assertTrue(shareSamplesPage.isNextButtonEnabled(), "Next button should be enabled"); @@ -41,7 +44,7 @@ public void testShareSamplesAsManager() { assertTrue(shareSamplesPage.isShareSingleSuccessDisplayed(), "Success message should be displayed"); // MOVING MULTIPLE SAMPLES - samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample5fg44"); samplesPage.selectSampleByName("sample554sg5"); samplesPage.selectSampleByName("sample5ddfg4"); @@ -69,7 +72,7 @@ public void testShareSamplesAsManager() { assertEquals(shareSamplesPage.getSuccessTitle(), "Successfully Moved Samples"); // MOVE SINGLE SAMPLE - samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample5fg44"); samplesPage.shareSamples(); @@ -85,7 +88,7 @@ public void testShareSamplesAsManager() { assertEquals(shareSamplesPage.getSuccessTitle(), "Successfully Moved 1 Sample"); // SHARING MULTIPLE SAMPLES - samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample_5_fg_22"); samplesPage.selectSampleByName("sample-5-fg-22"); samplesPage.selectSampleByName("sample5dt5"); @@ -101,7 +104,7 @@ public void testShareSamplesAsManager() { assertTrue(shareSamplesPage.isSuccessResultDisplayed(), "Success result should be displayed"); assertEquals(shareSamplesPage.getSuccessTitle(), "Successfully Shared Samples"); - samplesPage = ProjectSamplesPage.goToPage(driver(), 2); + samplesPage = ProjectSamplesPage.goToPage(driver(), 2L); samplesPage.selectSampleByName("sample5fg44"); samplesPage.shareSamples(); assertFalse(shareSamplesPage.isNextButtonEnabled(), @@ -125,12 +128,13 @@ void testSharingWithALockedSample() { LoginPage.loginAsManager(driver()); // SHARING SINGLE SAMPLE - ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1); + ProjectSamplesPage samplesPage = ProjectSamplesPage.goToPage(driver(), 1L); samplesPage.selectSampleByName("sample5fg44"); samplesPage.selectSampleByName(LOCKED_SAMPLE_NAME); samplesPage.shareSamples(); - assertFalse(shareSamplesPage.isNextButtonEnabled(), "The next button should not be enabled when going to the page"); + assertFalse(shareSamplesPage.isNextButtonEnabled(), + "The next button should not be enabled when going to the page"); shareSamplesPage.searchForProject("3"); assertThat(shareSamplesPage.getProjectSelectText()).contains("ID: 3"); assertTrue(shareSamplesPage.isNextButtonEnabled(), "Next button should be enabled"); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectUserGroupsPageIT.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectUserGroupsPageIT.java index 1fdae061721..a09d3cbcbfc 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectUserGroupsPageIT.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/integration/projects/ProjectUserGroupsPageIT.java @@ -68,12 +68,11 @@ public void testRemoteProjectPageAsAManager() { page.submitProject(); String pathTokens[] = driver().getCurrentUrl().split("/"); - Long projectId = Long.valueOf(pathTokens[pathTokens.length-1]); + Long projectId = Long.valueOf(pathTokens[pathTokens.length - 1]); ProjectMembersPage remoteProjectMembersPage = ProjectMembersPage.goToRemoteProject(driver(), projectId); assertEquals(1, remoteProjectMembersPage.getNumberOfMembers(), "Should be 1 members in the project"); - remoteProjectMembersPage.addUserToProject("Mr. Manager"); - remoteProjectMembersPage.updateUserRole(0, ProjectRole.PROJECT_OWNER.toString()); + remoteProjectMembersPage.addUserToProject("Mr. Manager", ProjectRole.PROJECT_OWNER.toString()); assertEquals(2, remoteProjectMembersPage.getNumberOfMembers(), "Should be 2 members in the project"); LoginPage.loginAsManager(driver()); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIMetadataFileImportServiceTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIMetadataFileImportServiceTest.java deleted file mode 100644 index 499625e15ba..00000000000 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIMetadataFileImportServiceTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package ca.corefacility.bioinformatics.irida.ria.unit.web.services; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; - -import ca.corefacility.bioinformatics.irida.model.project.Project; -import ca.corefacility.bioinformatics.irida.model.sample.Sample; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorageRow; -import ca.corefacility.bioinformatics.irida.ria.web.services.UIMetadataFileImportService; -import ca.corefacility.bioinformatics.irida.service.ProjectService; -import ca.corefacility.bioinformatics.irida.service.sample.SampleService; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -public class UIMetadataFileImportServiceTest { - private final Long PROJECT_ID = 1L; - private final Project project = new Project(); - private final Sample sample = new Sample(); - private UIMetadataFileImportService service; - private ProjectService projectService; - private SampleService sampleService; - - @BeforeEach - public void setUp() { - this.projectService = Mockito.mock(ProjectService.class); - this.sampleService = Mockito.mock(SampleService.class); - service = new UIMetadataFileImportService(projectService, sampleService); - } - - @Test - public void parseCSV() { - try { - SampleMetadataStorage expected_storage = getSampleMetadataStorage(); - - project.setId(PROJECT_ID); - when(projectService.read(PROJECT_ID)).thenReturn(project); - when(sampleService.getSampleBySampleName(project, "value2")).thenReturn(sample); - - MockMultipartFile file = new MockMultipartFile("file", "test.csv", MediaType.TEXT_PLAIN_VALUE, - "header1,header2,header3\nvalue1,value2,value3".getBytes()); - - byte[] byteArr = file.getBytes(); - InputStream inputStream = new ByteArrayInputStream(byteArr); - SampleMetadataStorage actual_storage = service.parseCSV(PROJECT_ID, inputStream); - assertEquals(actual_storage, expected_storage); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private SampleMetadataStorage getSampleMetadataStorage() { - SampleMetadataStorage expected_storage = new SampleMetadataStorage(); - List headers_list = new ArrayList<>(); - headers_list.add("header1"); - headers_list.add("header2"); - headers_list.add("header3"); - expected_storage.setHeaders(headers_list); - List rows = new ArrayList<>(); - Map rowMap = new HashMap<>(); - rowMap.put("header1", "value1"); - rowMap.put("header2", "value2"); - rowMap.put("header3", "value3"); - rows.add(new SampleMetadataStorageRow(rowMap)); - expected_storage.setRows(rows); - expected_storage.setSampleNameColumn("header2"); - return expected_storage; - } -} diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIProjectSampleServiceTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIProjectSampleServiceTest.java index 948c0170ef3..067f88ebbbd 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIProjectSampleServiceTest.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/services/UIProjectSampleServiceTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,10 +16,7 @@ import ca.corefacility.bioinformatics.irida.model.joins.impl.ProjectSampleJoin; import ca.corefacility.bioinformatics.irida.model.project.Project; import ca.corefacility.bioinformatics.irida.model.sample.Sample; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.CreateSampleRequest; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.SampleNameValidationResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.UpdateSampleRequest; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; +import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.*; import ca.corefacility.bioinformatics.irida.ria.web.ajax.projects.dto.MetadataEntryModel; import ca.corefacility.bioinformatics.irida.ria.web.services.UIProjectSampleService; import ca.corefacility.bioinformatics.irida.service.ProjectService; @@ -41,6 +39,8 @@ public class UIProjectSampleServiceTest { private final String BAD_NAME = "bad name with spaces"; private final String SHORT_NAME = "sho"; private final String GOOD_NAME = "good_name"; + private final String ORGANISM = "organism"; + private final String DESCRIPTION = "this is a description"; @BeforeEach public void setUp() { @@ -82,9 +82,46 @@ public void testValidateNewSampleName() { @Test public void testCreateSample() { - CreateSampleRequest request = new CreateSampleRequest(GOOD_NAME, null); - ResponseEntity response = service.createSample(request, PROJECT_1_ID, Locale.ENGLISH); - assertEquals(HttpStatus.OK, response.getStatusCode(), "Sample should be created"); + CreateSampleRequest[] requests = { new CreateSampleRequest(GOOD_NAME, null) }; + Map responses = service.createSamples(requests, PROJECT_1_ID); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be created"); + } + + @Test + public void testCreateSampleWithOrganism() { + CreateSampleRequest[] requests = { new CreateSampleRequest(GOOD_NAME, ORGANISM) }; + Map responses = service.createSamples(requests, PROJECT_1_ID); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be created"); + } + + @Test + public void testCreateSampleWithDescription() { + CreateSampleRequest[] requests = { new CreateSampleRequest(GOOD_NAME, null, DESCRIPTION, null) }; + Map responses = service.createSamples(requests, PROJECT_1_ID); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be created"); + } + + @Test + public void testCreateSampleWithOrganismAndDescription() { + CreateSampleRequest[] requests = { new CreateSampleRequest(GOOD_NAME, ORGANISM, DESCRIPTION, null) }; + Map responses = service.createSamples(requests, PROJECT_1_ID); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be created"); } @Test @@ -92,9 +129,13 @@ public void testCreateSampleWithMetadata() { List metadata = new ArrayList<>(); metadata.add(new MetadataEntryModel("field1", "value1")); metadata.add(new MetadataEntryModel("field2", "value2")); - CreateSampleRequest request = new CreateSampleRequest(GOOD_NAME, null, null, metadata); - ResponseEntity response = service.createSample(request, PROJECT_1_ID, Locale.ENGLISH); - assertEquals(HttpStatus.OK, response.getStatusCode(), "Sample should be created"); + CreateSampleRequest[] requests = { new CreateSampleRequest(GOOD_NAME, null, null, metadata) }; + Map responses = service.createSamples(requests, PROJECT_1_ID); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((CreateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be created"); } @Test @@ -102,8 +143,12 @@ public void testUpdateSampleWithMetadata() { List metadata = new ArrayList<>(); metadata.add(new MetadataEntryModel("field1", "value1")); metadata.add(new MetadataEntryModel("field2", "value2")); - UpdateSampleRequest request = new UpdateSampleRequest(GOOD_NAME, null, null, metadata); - ResponseEntity response = service.updateSample(request, SAMPLE_1_ID, Locale.ENGLISH); - assertEquals(HttpStatus.OK, response.getStatusCode(), "Sample should be updated"); + UpdateSampleRequest[] requests = { new UpdateSampleRequest(SAMPLE_1_ID, GOOD_NAME, null, null, metadata) }; + Map responses = service.updateSamples(requests); + long errorCount = responses.entrySet() + .stream() + .filter(response -> ((UpdateSampleResponse) response.getValue()).isError()) + .count(); + assertEquals(0, errorCount, "Sample should be updated"); } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/web/controller/test/unit/projects/metadata/ProjectSampleMetadataAjaxControllerTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/web/controller/test/unit/projects/metadata/ProjectSampleMetadataAjaxControllerTest.java deleted file mode 100644 index f9be7db47fd..00000000000 --- a/src/test/java/ca/corefacility/bioinformatics/irida/web/controller/test/unit/projects/metadata/ProjectSampleMetadataAjaxControllerTest.java +++ /dev/null @@ -1,174 +0,0 @@ -package ca.corefacility.bioinformatics.irida.web.controller.test.unit.projects.metadata; - -import java.util.*; - -import javax.servlet.http.HttpSession; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.context.MessageSource; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockMultipartFile; - -import ca.corefacility.bioinformatics.irida.model.project.Project; -import ca.corefacility.bioinformatics.irida.model.sample.Sample; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorage; -import ca.corefacility.bioinformatics.irida.ria.utilities.SampleMetadataStorageRow; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxResponse; -import ca.corefacility.bioinformatics.irida.ria.web.ajax.dto.ajax.AjaxSuccessResponse; -import ca.corefacility.bioinformatics.irida.ria.web.projects.metadata.ProjectSampleMetadataAjaxController; -import ca.corefacility.bioinformatics.irida.ria.web.services.UIMetadataFileImportService; -import ca.corefacility.bioinformatics.irida.ria.web.services.UIMetadataImportService; -import ca.corefacility.bioinformatics.irida.service.ProjectService; -import ca.corefacility.bioinformatics.irida.service.sample.MetadataTemplateService; -import ca.corefacility.bioinformatics.irida.service.sample.SampleService; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.*; - -public class ProjectSampleMetadataAjaxControllerTest { - private ProjectSampleMetadataAjaxController controller; - private UIMetadataImportService metadataImportService; - private MessageSource messageSource; - private ProjectService projectService; - private SampleService sampleService; - private MetadataTemplateService metadataTemplateService; - private UIMetadataFileImportService metadataFileImportService; - private HttpSession session; - - private final Long PROJECT_ID = 1L; - private final Long SAMPLE_ID = 1L; - private final String SAMPLE_NAME = "value2"; - private final String SAMPLE_NAME_COLUMN = "header2"; - - @BeforeEach - public void setUp() { - session = mock(HttpSession.class); - messageSource = mock(MessageSource.class); - projectService = mock(ProjectService.class); - sampleService = mock(SampleService.class); - metadataTemplateService = mock(MetadataTemplateService.class); - metadataFileImportService = new UIMetadataFileImportService(projectService, sampleService); - metadataImportService = new UIMetadataImportService(messageSource, projectService, sampleService, - metadataTemplateService, metadataFileImportService); - controller = new ProjectSampleMetadataAjaxController(metadataImportService); - } - - private SampleMetadataStorage createSampleMetadataStorage() { - SampleMetadataStorage expected_storage = new SampleMetadataStorage(); - List headers_list = new ArrayList<>(); - headers_list.add("header1"); - headers_list.add("header2"); - headers_list.add("header3"); - expected_storage.setHeaders(headers_list); - List rows = new ArrayList<>(); - Map rowMap = new HashMap<>(); - rowMap.put("header1", "value1"); - rowMap.put("header2", "value2"); - rowMap.put("header3", "value3"); - rows.add(new SampleMetadataStorageRow(rowMap)); - expected_storage.setRows(rows); - expected_storage.setSampleNameColumn(SAMPLE_NAME_COLUMN); - return expected_storage; - } - - private Sample createSample() { - Sample sample = new Sample(); - sample.setSampleName(SAMPLE_NAME); - sample.setId(SAMPLE_ID); - return sample; - } - - private Project createProject() { - Project project = new Project(); - project.setId(PROJECT_ID); - return project; - } - - @Test - public void createProjectSampleMetadataTest() throws Exception { - MockMultipartFile file = new MockMultipartFile("file", "test.csv", MediaType.TEXT_PLAIN_VALUE, - "header1,header2,header3\nvalue1,value2,value3".getBytes()); - SampleMetadataStorage stored = createSampleMetadataStorage(); - - when(session.getAttribute("pm-" + PROJECT_ID)).thenReturn(stored); - - ResponseEntity response = controller.createProjectSampleMetadata(session, PROJECT_ID, - file); - stored = (SampleMetadataStorage) session.getAttribute("pm-" + PROJECT_ID); - - assertEquals(response.getStatusCode(), HttpStatus.OK, "Receive an 200 OK response"); - assertEquals(stored.getRows().size(), 1, "Sample name columns is saved"); - assertEquals(stored.getHeaders().size(), 3, "Sample is saved"); - } - - @Test - public void setProjectSampleMetadataSampleIdTest() { - SampleMetadataStorage stored = createSampleMetadataStorage(); - Sample sample = createSample(); - Project project = createProject(); - - when(session.getAttribute("pm-" + PROJECT_ID)).thenReturn(stored); - when(projectService.read(project.getId())).thenReturn(project); - when(sampleService.getSampleBySampleName(project, sample.getSampleName())).thenReturn(sample); - - ResponseEntity response = controller.setProjectSampleMetadataSampleId(session, PROJECT_ID, - SAMPLE_NAME_COLUMN); - stored = (SampleMetadataStorage) session.getAttribute("pm-" + PROJECT_ID); - - assertEquals(response.getStatusCode(), HttpStatus.OK, "Receive an 200 OK response"); - assertEquals(((AjaxSuccessResponse) response.getBody()).getMessage(), "complete", "Receive a complete message"); - assertEquals(stored.getSampleNameColumn(), SAMPLE_NAME_COLUMN, "Sample name columns is saved"); - assertEquals((long) stored.getRow(sample.getSampleName(), SAMPLE_NAME_COLUMN).getFoundSampleId(), - (long) sample.getId(), "Found sample id is saved"); - } - - @Test - public void saveProjectSampleMetadataTest() { - Sample sample = createSample(); - List sampleNames = List.of(sample.getSampleName()); - Locale locale = new Locale("en"); - SampleMetadataStorage stored = createSampleMetadataStorage(); - when(session.getAttribute("pm-" + PROJECT_ID)).thenReturn(stored); - when(messageSource.getMessage("project.samples.table.sample-id", new Object[] {}, locale)) - .thenReturn("Sample Id"); - when(messageSource.getMessage("project.samples.table.id", new Object[] {}, locale)).thenReturn("ID"); - when(messageSource.getMessage("project.samples.table.modified-date", new Object[] {}, locale)) - .thenReturn("Modified Date"); - when(messageSource.getMessage("project.samples.table.modified", new Object[] {}, locale)) - .thenReturn("Modified On"); - when(messageSource.getMessage("project.samples.table.created-date", new Object[] {}, locale)) - .thenReturn("Created Date"); - when(messageSource.getMessage("project.samples.table.created", new Object[] {}, locale)) - .thenReturn("Created On"); - when(messageSource.getMessage("project.samples.table.coverage", new Object[] {}, locale)) - .thenReturn("Coverage"); - when(messageSource.getMessage("project.samples.table.project-id", new Object[] {}, locale)) - .thenReturn("Project ID"); - - ResponseEntity response = controller.saveProjectSampleMetadata(locale, session, PROJECT_ID, - sampleNames); - stored = (SampleMetadataStorage) session.getAttribute("pm-" + PROJECT_ID); - - assertEquals(response.getStatusCode(), HttpStatus.OK, "Receive an 200 OK response"); - assertTrue(stored.getRow(sample.getSampleName(), SAMPLE_NAME_COLUMN).isSaved(), "Sample is saved"); - } - - @Test - public void clearProjectSampleMetadataTest() { - controller.clearProjectSampleMetadata(session, PROJECT_ID); - - verify(session, times(1)).removeAttribute("pm-" + PROJECT_ID); - } - - @Test - public void getProjectSampleMetadataTest() { - ResponseEntity response = controller.getProjectSampleMetadata(session, PROJECT_ID); - - assertEquals(response.getStatusCode(), HttpStatus.OK, "Receive an 200 OK response"); - verify(session, times(1)).getAttribute("pm-" + PROJECT_ID); - } -} diff --git a/src/test/resources/ca/corefacility/bioinformatics/irida/ria/web/projects/ProjectSampleMetadataView.xml b/src/test/resources/ca/corefacility/bioinformatics/irida/ria/web/projects/ProjectSampleMetadataView.xml index 5297b7456ed..5d0b1793e12 100644 --- a/src/test/resources/ca/corefacility/bioinformatics/irida/ria/web/projects/ProjectSampleMetadataView.xml +++ b/src/test/resources/ca/corefacility/bioinformatics/irida/ria/web/projects/ProjectSampleMetadataView.xml @@ -1,9 +1,9 @@ - + - + @@ -24,6 +24,7 @@ + @@ -31,49 +32,46 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/ca/corefacility/bioinformatics/irida/test/integration/TableReset.xml b/src/test/resources/ca/corefacility/bioinformatics/irida/test/integration/TableReset.xml index 6d97954755a..dd04465b732 100644 --- a/src/test/resources/ca/corefacility/bioinformatics/irida/test/integration/TableReset.xml +++ b/src/test/resources/ca/corefacility/bioinformatics/irida/test/integration/TableReset.xml @@ -99,6 +99,8 @@ + +