Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GLT-4050] switched over from requisition QCs to deliverables #178

Merged
merged 7 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "Debug local Dimsum",
"request": "attach",
"hostName": "localhost",
"port": 8083
"port": 8000
}
]
}
1 change: 1 addition & 0 deletions changes/add_deliverableTypeFilters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Separate options for Data Release and Clinical Report in Pending, Completed, and Incomplete filters
1 change: 1 addition & 0 deletions changes/add_nabuSignoff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ability to sign off cases for Analysis Review, Release Approval, and Release directly from Dimsum
3 changes: 3 additions & 0 deletions changes/change_deliverable_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Analysis review and release approval sign-offs are now tied to a deliverable type - Clinical Report
or Data Release. Separate sign-offs are required for each deliverable type that is configured for
the project
2 changes: 2 additions & 0 deletions changes/change_deliverables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Release sign-offs are now tied to a deliverable such as a clinical report or fastQs. Separate
sign-offs are required for each deliverable that is configured for the project
1 change: 1 addition & 0 deletions changes/fix_pendingWork_previousSkipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The pending work icon was not appearing when the previous step was "N/A"
1 change: 1 addition & 0 deletions changes/fix_projectPendingCount_stoppedRelease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pending Release Approvals and Releases were not counted for stopped cases in the project summary
1 change: 1 addition & 0 deletions example-application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ saml.idpmetadataurl=
saml.ssourl=
saml.spkey=
saml.spcert=
saml.usernameattribute=

## JIRA integration
## if baseurl is set, all other jira properties are also required
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"devDependencies": {
"tailwindcss": "^3.0.24",
"ts-loader": "^9.3.0",
"typescript": "^4.6.4",
"typescript": "^5.3.3",
"webpack": "^5.76.0",
"webpack-cli": "^4.9.2"
}
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
<dependency>
<groupId>ca.on.oicr.gsi.cardea</groupId>
<artifactId>cardea-data</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
</dependencies>

Expand Down
93 changes: 51 additions & 42 deletions src/main/java/ca/on/oicr/gsi/dimsum/CaseLoader.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ca.on.oicr.gsi.dimsum;

import java.io.IOException;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -22,14 +22,12 @@
import ca.on.oicr.gsi.cardea.data.Case;
import ca.on.oicr.gsi.cardea.data.OmittedSample;
import ca.on.oicr.gsi.cardea.data.Project;
import ca.on.oicr.gsi.cardea.data.RequisitionQc;
import ca.on.oicr.gsi.cardea.data.RunAndLibraries;
import ca.on.oicr.gsi.cardea.data.Sample;
import ca.on.oicr.gsi.cardea.data.Test;
import ca.on.oicr.gsi.dimsum.data.CaseData;
import ca.on.oicr.gsi.dimsum.data.ProjectSummary;
import ca.on.oicr.gsi.dimsum.service.filtering.CompletedGate;
import ca.on.oicr.gsi.dimsum.service.filtering.DateFilter;
import ca.on.oicr.gsi.dimsum.service.filtering.PendingState;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
Expand Down Expand Up @@ -81,7 +79,7 @@ public CaseData load(ZonedDateTime previousTimestamp) throws IOException {
Set<String> donorNames = loadDonorNames(cardeaCaseData.getCases());
Set<String> testNames = getTestNames(cardeaCaseData.getCases());
Map<String, ProjectSummary> projectSummariesByName =
calculateProjectSummaries(cardeaCaseData.getCases(), null);
calculateProjectSummaries(cardeaCaseData.getCases(), null, null);
ZonedDateTime afterTimestamp = cardeaCaseData.getTimestamp();

CaseData caseData =
Expand Down Expand Up @@ -181,29 +179,29 @@ private static interface ParseFunction<T, R> {
}

public static Map<String, ProjectSummary> calculateProjectSummaries(List<Case> cases,
Collection<DateFilter> dateFilters) {
LocalDate afterDate, LocalDate beforeDate) {
Map<String, ProjectSummary.Builder> tempProjectSummariesByName = new HashMap<>();
for (Case kase : cases) {
addCounts(kase, kase.getTests(), tempProjectSummariesByName, dateFilters);
addCounts(kase, kase.getTests(), tempProjectSummariesByName, afterDate, beforeDate);
}

return buildProjectSummaries(tempProjectSummariesByName);
}

public static Map<String, ProjectSummary> calculateFilteredProjectSummaries(
Map<Case, List<Test>> map, Collection<DateFilter> dateFilters) {
Map<Case, List<Test>> map, LocalDate afterDate, LocalDate beforeDate) {
Map<String, ProjectSummary.Builder> tempProjectSummariesByName = new HashMap<>();
List<Case> cases = new ArrayList<>(map.keySet());
for (Case kase : cases) {
addCounts(kase, map.get(kase), tempProjectSummariesByName, dateFilters);
addCounts(kase, map.get(kase), tempProjectSummariesByName, afterDate, beforeDate);
}
return buildProjectSummaries(tempProjectSummariesByName);

}

private static void addCounts(Case kase, List<Test> tests,
Map<String, ProjectSummary.Builder> tempProjectSummariesByName,
Collection<DateFilter> dateFilters) {
Map<String, ProjectSummary.Builder> tempProjectSummariesByName, LocalDate afterDate,
LocalDate beforeDate) {
ProjectSummary.Builder caseSummary =
new ProjectSummary.Builder();
int testSize = tests != null ? tests.size() : 0;
Expand All @@ -212,13 +210,13 @@ private static void addCounts(Case kase, List<Test> tests,
caseSummary.receiptPendingQcCount(testSize);
}
if (CompletedGate.RECEIPT.qualifyCase(kase)
&& anySamplesMatch(kase.getReceipts(), dateFilters)) {
&& anySamplesMatch(kase.getReceipts(), afterDate, beforeDate)) {
caseSummary.receiptCompletedCount(testSize);
}
for (Test test : tests) {
if ((test.isExtractionSkipped() && emptyOrNull(dateFilters))
if ((test.isExtractionSkipped() && afterDate == null && beforeDate == null)
|| (CompletedGate.EXTRACTION.qualifyTest(test)
&& anySamplesMatch(test.getExtractions(), dateFilters))) {
&& anySamplesMatch(test.getExtractions(), afterDate, beforeDate))) {
caseSummary.incrementExtractionCompletedCount();
} else if (PendingState.EXTRACTION_QC.qualifyTest(test) && !kase.isStopped()) {
caseSummary.incrementExtractionPendingQcCount();
Expand All @@ -227,9 +225,9 @@ && anySamplesMatch(test.getExtractions(), dateFilters))) {
}

// library Preparation
if ((test.isLibraryPreparationSkipped() && emptyOrNull(dateFilters))
if ((test.isLibraryPreparationSkipped() && afterDate == null && beforeDate == null)
|| (CompletedGate.LIBRARY_PREPARATION.qualifyTest(test)
&& anySamplesMatch(test.getLibraryPreparations(), dateFilters))) {
&& anySamplesMatch(test.getLibraryPreparations(), afterDate, beforeDate))) {
caseSummary.incrementLibraryPrepCompletedCount();
} else if (PendingState.LIBRARY_QC.qualifyTest(test) && !kase.isStopped()) {
caseSummary.incrementLibraryPrepPendingQcCount();
Expand All @@ -239,7 +237,7 @@ && anySamplesMatch(test.getLibraryPreparations(), dateFilters))) {

// Library Qualification
if (CompletedGate.LIBRARY_QUALIFICATION.qualifyTest(test)
&& anySamplesMatch(test.getLibraryQualifications(), dateFilters)) {
&& anySamplesMatch(test.getLibraryQualifications(), afterDate, beforeDate)) {
caseSummary.incrementLibraryQualCompletedCount();
} else if ((PendingState.LIBRARY_QUALIFICATION_QC.qualifyTest(test)
|| PendingState.LIBRARY_QUALIFICATION_DATA_REVIEW.qualifyTest(test))
Expand All @@ -251,7 +249,7 @@ && anySamplesMatch(test.getLibraryQualifications(), dateFilters)) {

// Full depth sequncing
if (CompletedGate.FULL_DEPTH_SEQUENCING.qualifyTest(test)
&& anySamplesMatch(test.getFullDepthSequencings(), dateFilters)) {
&& anySamplesMatch(test.getFullDepthSequencings(), afterDate, beforeDate)) {
caseSummary.incrementFullDepthSeqCompletedCount();
} else if ((PendingState.FULL_DEPTH_QC.qualifyTest(test)
|| PendingState.FULL_DEPTH_DATA_REVIEW.qualifyTest(test)) && !kase.isStopped()) {
Expand All @@ -263,7 +261,8 @@ && anySamplesMatch(test.getFullDepthSequencings(), dateFilters)) {

// analysis review
if (CompletedGate.ANALYSIS_REVIEW.qualifyCase(kase)
&& anyRequisitionQcsMatch(kase.getRequisition().getAnalysisReviews(), dateFilters)) {
&& kase.getDeliverables().stream()
.anyMatch(x -> dateBetween(x.getAnalysisReviewQcDate(), afterDate, beforeDate))) {
caseSummary.analysisReviewCompletedCount(testSize);
}
if (PendingState.ANALYSIS_REVIEW.qualifyCase(kase) && !kase.isStopped()) {
Expand All @@ -272,19 +271,22 @@ && anyRequisitionQcsMatch(kase.getRequisition().getAnalysisReviews(), dateFilter

// release approval
if (CompletedGate.RELEASE_APPROVAL.qualifyCase(kase)
&& anyRequisitionQcsMatch(kase.getRequisition().getReleaseApprovals(), dateFilters)) {
&& kase.getDeliverables().stream()
.anyMatch(x -> Boolean.TRUE.equals(x.getReleaseApprovalQcPassed()))) {
caseSummary.releaseApprovalCompletedCount(testSize);
}
if (PendingState.RELEASE_APPROVAL.qualifyCase(kase) && !kase.isStopped()) {
if (PendingState.RELEASE_APPROVAL.qualifyCase(kase)) {
caseSummary.releaseApprovalPendingCount(testSize);
}

// release
if (CompletedGate.RELEASE.qualifyCase(kase)
&& anyRequisitionQcsMatch(kase.getRequisition().getReleases(), dateFilters)) {
&& kase.getDeliverables().stream()
.anyMatch(d -> d.getReleases() != null && d.getReleases().stream()
.anyMatch(r -> dateBetween(r.getQcDate(), afterDate, beforeDate)))) {
caseSummary.releaseCompletedCount(testSize);
}
if (PendingState.RELEASE.qualifyCase(kase) && !kase.isStopped()) {
if (PendingState.RELEASE.qualifyCase(kase)) {
caseSummary.releasePendingCount(testSize);
}

Expand All @@ -303,6 +305,20 @@ && anyRequisitionQcsMatch(kase.getRequisition().getReleases(), dateFilters)) {
}
}

protected static boolean dateBetween(LocalDate date, LocalDate afterDate, LocalDate beforeDate) {
// Note: afterDate is inclusive, but beforeDate is exclusive
if (date == null) {
return false;
}
if (afterDate != null && date.isBefore(afterDate)) {
return false;
}
if (beforeDate != null && !beforeDate.isAfter(date)) {
return false;
}
return true;
}

private static Map<String, ProjectSummary> buildProjectSummaries(
Map<String, ProjectSummary.Builder> tempProjectSummariesByName) {
Map<String, ProjectSummary> projectSummariesByName = new HashMap<>();
Expand All @@ -313,27 +329,20 @@ private static Map<String, ProjectSummary> buildProjectSummaries(
return projectSummariesByName;
}

private static boolean anySamplesMatch(List<Sample> samples, Collection<DateFilter> filters) {
if (filters == null || filters.isEmpty()) {
return !samples.isEmpty();
}
return samples.stream()
.anyMatch(sample -> filters.stream()
.allMatch(filter -> filter.samplePredicate().test(sample)));
}

private static boolean anyRequisitionQcsMatch(List<RequisitionQc> requisitionQcs,
Collection<DateFilter> filters) {
if (filters == null || filters.isEmpty()) {
return !requisitionQcs.isEmpty();
private static boolean anySamplesMatch(List<Sample> samples, LocalDate afterDate,
LocalDate beforeDate) {
if (samples.isEmpty()) {
return false;
} else if (afterDate == null && beforeDate == null) {
return true;
}
return requisitionQcs.stream()
.anyMatch(qc -> filters.stream()
.allMatch(filter -> filter.requisitionQcPredicate().test(qc)));
}

private static boolean emptyOrNull(Collection<?> collection) {
return collection == null || collection.isEmpty();
return samples.stream().anyMatch(sample -> {
LocalDate qcDate = sample.getRun() == null ? sample.getQcDate() : sample.getDataReviewDate();
if (qcDate == null) {
return false;
}
return dateBetween(qcDate, afterDate, beforeDate);
});
}

}
41 changes: 18 additions & 23 deletions src/main/java/ca/on/oicr/gsi/dimsum/controller/mvc/MvcUtils.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package ca.on.oicr.gsi.dimsum.controller.mvc;

import java.time.LocalDate;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import ca.on.oicr.gsi.dimsum.controller.BadRequestException;
import ca.on.oicr.gsi.dimsum.controller.rest.request.DataQuery;
import ca.on.oicr.gsi.dimsum.controller.rest.request.KeyValuePair;
import ca.on.oicr.gsi.dimsum.service.filtering.CaseFilter;
import ca.on.oicr.gsi.dimsum.service.filtering.CaseFilterKey;
import ca.on.oicr.gsi.dimsum.service.filtering.DateFilter;
import ca.on.oicr.gsi.dimsum.service.filtering.DateFilterKey;
import ca.on.oicr.gsi.dimsum.service.filtering.OmittedSampleFilter;
import ca.on.oicr.gsi.dimsum.service.filtering.OmittedSampleFilterKey;
import ca.on.oicr.gsi.dimsum.service.filtering.ProjectSummaryFilter;
Expand All @@ -18,8 +16,9 @@
import ca.on.oicr.gsi.dimsum.service.filtering.RunFilterKey;

public class MvcUtils {
private final static List<String> dateFilterKeys =
Stream.of(DateFilterKey.values()).map(DateFilterKey::name).toList();

private static final String BEFORE_DATE_KEY = "BEFORE_DATE";
private static final String AFTER_DATE_KEY = "AFTER_DATE";


public static void validateDataQuery(DataQuery query) {
Expand Down Expand Up @@ -67,7 +66,8 @@ public static List<CaseFilter> parseCaseFiltersForProject(DataQuery query) {
// exclude Date Range Filters before validating each filter KVP
List<KeyValuePair> queryFilters =
query.getFilters().stream()
.filter(x -> !dateFilterKeys.contains(x.getKey())).toList();
.filter(x -> !BEFORE_DATE_KEY.equals(x.getKey()) && !AFTER_DATE_KEY.equals(x.getKey()))
.toList();
if (queryFilters == null || queryFilters.isEmpty()) {
return null;
}
Expand Down Expand Up @@ -118,25 +118,20 @@ private static OmittedSampleFilter parseOmittedSampleFilter(KeyValuePair pair) {
}
}

public static List<DateFilter> parseDateFilters(DataQuery query) {
// exclude Date Range Filters before validating each filter KVP
List<KeyValuePair> queryFilters =
query.getFilters().stream()
.filter(x -> dateFilterKeys.contains(x.getKey())).toList();
if (queryFilters == null || queryFilters.isEmpty()) {
return null;
}
return queryFilters.stream()
.map(MvcUtils::parseDateFilter).toList();
public static LocalDate parseAfterDate(DataQuery query) {
return parseDateFilterValue(query, AFTER_DATE_KEY);
}

private static DateFilter parseDateFilter(KeyValuePair pair) {
try {
DateFilterKey key = DateFilterKey.valueOf(pair.getKey());
return new DateFilter(key, pair.getValue());
} catch (IllegalArgumentException e) {
throw new BadRequestException(String.format("Invalid filter key: %s", pair.getKey()));
}
public static LocalDate parseBeforeDate(DataQuery query) {
return parseDateFilterValue(query, BEFORE_DATE_KEY);
}

private static LocalDate parseDateFilterValue(DataQuery query, String key) {
String value = query.getFilters().stream()
.filter(x -> key.equals(x.getKey()))
.map(KeyValuePair::getValue)
.findFirst().orElse(null);
return value == null ? null : LocalDate.parse(value);
}

public static List<ProjectSummaryFilter> parseProjectSummaryFilters(DataQuery query) {
Expand Down
Loading
Loading