Skip to content

Commit

Permalink
Add result and condition filters to test results list (#6682)
Browse files Browse the repository at this point in the history
* Refactor results table - each multiplex result on own row

* Rename TestResultsList test titles to reflect refactor; remove obsoleted test

* Add skeleton structure for returning Results by facility & organization

* Add result service methods to return results with various filters applied

* Adds support for GraphQL query to filter and return granular test result
data
  - Adds resolver method to return paginated test result data
  - Adds a utility `TestResultsListItem` class - this is the object
    returned by the server
  - Adds a data resolver to correctly fetch facility data for a test
    result

* Defines query operation to retrieve filtered test results from server; updates to expected shape of incoming test result in result table UI component

* Fix page & result count display; Tidy comments & un-used code

* Remove reference to non-existent generated GraphQL method

* Test changes - update many, many mocks and fudge some typing to get things to build (for now)

* Fix reference to test id in mock

* Update some more test results mocks

* Update sooooo many mocks, other test tweaks

* comment out some junk so I can build

* Broke out new ResultService tests into nested block to keep setup separate from existing tests

* Correct mocks for ResultsTable unit tests

* Update test result CSV download mock

* Actually correct mock response for CSV results download

* Cleanup....

* Add ResultService test for date range filter; clean up some code smells

* Remove unused file

* More code smell cleanup, on the frontend this time

* Remove unread local variables in unit test file

* Add some (not super-useful) unit tests for ResultResolver

* Get patient link via GraphQL query for test result row in e2e tests

* Un-comment things from testing

* Run login hooks for patient link e2e test - needed for one-off GraphQL query from Cypress

* Add and employ a GraphQL query called from Cypress to get patient link for a result

* Try to get the right envvar for the SR frontend...

* Ditto

* once more, with feeling

* Get rid of some stuff that should not be there

* Remove some commented-out code and unused test result item properties

* Remove unnecessary assertion in TestResultsList unit tests

* Codegen

* Remove some unused properties on mock objects for ResultsTable
  • Loading branch information
nathancrtr authored Oct 25, 2023
1 parent 2472259 commit 8353d11
Show file tree
Hide file tree
Showing 41 changed files with 1,650 additions and 951 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gov.cdc.usds.simplereport.api.testresult;

import gov.cdc.usds.simplereport.api.model.ApiFacility;
import gov.cdc.usds.simplereport.db.model.auxiliary.TestResultsListItem;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

@Controller
public class ResultDataResolver {

@SchemaMapping(typeName = "Result", field = "facility")
public ApiFacility getFacility(TestResultsListItem result) {
return new ApiFacility(result.getFacility());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package gov.cdc.usds.simplereport.api.testresult;

import gov.cdc.usds.simplereport.api.Translators;
import gov.cdc.usds.simplereport.db.model.SupportedDisease;
import gov.cdc.usds.simplereport.db.model.auxiliary.TestResultsListItem;
import gov.cdc.usds.simplereport.service.DiseaseService;
import gov.cdc.usds.simplereport.service.ResultService;
import gov.cdc.usds.simplereport.service.TestOrderService;
import java.util.Date;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
@RequiredArgsConstructor
public class ResultResolver {

private final ResultService service;
private final DiseaseService diseaseService;

@QueryMapping
public Page<TestResultsListItem> resultsPage(
@Argument UUID facilityId,
@Argument UUID patientId,
@Argument String result,
@Argument String role,
@Argument String disease,
@Argument Date startDate,
@Argument Date endDate,
@Argument int pageNumber,
@Argument int pageSize) {

if (pageNumber < 0) {
pageNumber = TestOrderService.DEFAULT_PAGINATION_PAGEOFFSET;
}

if (pageSize < 1) {
pageSize = TestOrderService.DEFAULT_PAGINATION_PAGESIZE;
}

SupportedDisease supportedDisease =
disease != null ? diseaseService.getDiseaseByName(disease) : null;

if (facilityId == null) {
return service
.getOrganizationResults(
patientId,
Translators.parseTestResult(result),
Translators.parsePersonRole(role, true),
supportedDisease,
startDate,
endDate,
pageNumber,
pageSize)
.map(TestResultsListItem::new);
}

return service
.getFacilityResults(
facilityId,
patientId,
Translators.parseTestResult(result),
Translators.parsePersonRole(role, true),
supportedDisease,
startDate,
endDate,
pageNumber,
pageSize)
.map(TestResultsListItem::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gov.cdc.usds.simplereport.db.model.auxiliary;

import gov.cdc.usds.simplereport.db.model.ApiUser;
import gov.cdc.usds.simplereport.db.model.DeviceType;
import gov.cdc.usds.simplereport.db.model.Facility;
import gov.cdc.usds.simplereport.db.model.Person;
import gov.cdc.usds.simplereport.db.model.Result;
import java.util.Date;
import java.util.UUID;
import lombok.Getter;

@Getter
public class TestResultsListItem {
private final UUID id;
private final Facility facility;
private final Person patient;
private final Date dateAdded;
private final Date dateUpdated;
private final DeviceType deviceType;
private final String disease;
private final TestResult testResult;
private final Date dateTested;
private final TestCorrectionStatus correctionStatus;
private final String reasonForCorrection;
private final ApiUser createdBy;

public TestResultsListItem(Result result) {
this.id = result.getTestEvent().getInternalId();
this.facility = result.getTestEvent().getFacility();
this.patient = result.getTestEvent().getPatient();
this.dateAdded = result.getTestEvent().getDateTested();
this.dateUpdated = result.getUpdatedAt();
this.deviceType = result.getTestEvent().getDeviceType();
this.disease = result.getDisease().getName();
this.testResult = result.getTestResult();
this.dateTested = result.getTestEvent().getDateTested();
this.correctionStatus = result.getTestEvent().getCorrectionStatus();
this.reasonForCorrection = result.getTestEvent().getReasonForCorrection();
this.createdBy = result.getTestEvent().getCreatedBy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
import gov.cdc.usds.simplereport.db.model.TestOrder;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;

public interface ResultRepository extends EternalAuditedEntityRepository<Result> {

Page<Result> findAll(Specification<Result> searchSpec, Pageable p);

List<Result> findAllByTestEvent(TestEvent testEvent);

List<Result> findAllByTestOrder(TestOrder testOrder);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package gov.cdc.usds.simplereport.service;

import gov.cdc.usds.simplereport.config.AuthorizationConfiguration;
import gov.cdc.usds.simplereport.db.model.AuditedEntity_;
import gov.cdc.usds.simplereport.db.model.BaseTestInfo_;
import gov.cdc.usds.simplereport.db.model.IdentifiedEntity_;
import gov.cdc.usds.simplereport.db.model.Person;
import gov.cdc.usds.simplereport.db.model.Person_;
import gov.cdc.usds.simplereport.db.model.Result;
import gov.cdc.usds.simplereport.db.model.Result_;
import gov.cdc.usds.simplereport.db.model.SupportedDisease;
import gov.cdc.usds.simplereport.db.model.TestEvent;
import gov.cdc.usds.simplereport.db.model.TestEvent_;
import gov.cdc.usds.simplereport.db.model.TestOrder;
import gov.cdc.usds.simplereport.db.model.auxiliary.PersonRole;
import gov.cdc.usds.simplereport.db.model.auxiliary.TestResult;
import gov.cdc.usds.simplereport.db.repository.ResultRepository;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -16,6 +35,140 @@
@Transactional
public class ResultService {
private final ResultRepository resultRepository;
private final DiseaseService diseaseService;
private final OrganizationService organizationService;

private Specification<Result> buildResultSearchFilter(
UUID facilityId,
UUID patientId,
TestResult result,
PersonRole role,
SupportedDisease disease,
Date startDate,
Date endDate) {
return (root, query, cb) -> {
Join<Result, TestEvent> testEventJoin = root.join(Result_.testEvent);
Join<TestEvent, Person> personJoin = testEventJoin.join(TestEvent_.patient);
Predicate p = cb.conjunction();

query.orderBy(cb.desc(root.get(AuditedEntity_.createdAt)));
query.distinct(true);

if (facilityId != null) {
p =
cb.and(
p,
cb.equal(
testEventJoin.get(BaseTestInfo_.facility).get(IdentifiedEntity_.internalId),
facilityId));
} else {
final UUID finalOrgId = organizationService.getCurrentOrganization().getInternalId();

p =
cb.and(
p,
cb.equal(
testEventJoin.get(BaseTestInfo_.organization).get(IdentifiedEntity_.internalId),
finalOrgId));
}

if (patientId != null) {
p =
cb.and(
p,
cb.equal(
testEventJoin.get(BaseTestInfo_.patient).get(IdentifiedEntity_.internalId),
patientId));
}

if (result != null) {
p = cb.and(p, cb.equal(root.get(Result_.testResult), result));
}

if (role != null) {
p = cb.and(p, cb.equal(personJoin.get(Person_.role), role));
}

if (disease != null) {
p = cb.and(p, cb.equal(root.get(Result_.disease), disease));
}

if (startDate != null) {
p =
cb.and(
p,
cb.or(
cb.and(
cb.isNotNull(testEventJoin.get(BaseTestInfo_.dateTestedBackdate)),
cb.greaterThanOrEqualTo(
testEventJoin.get(BaseTestInfo_.dateTestedBackdate), startDate)),
cb.and(
cb.isNull(testEventJoin.get(BaseTestInfo_.dateTestedBackdate)),
cb.greaterThanOrEqualTo(
testEventJoin.get(AuditedEntity_.createdAt), startDate))));
}

if (endDate != null) {
p =
cb.and(
p,
cb.or(
cb.and(
cb.isNotNull(testEventJoin.get(BaseTestInfo_.dateTestedBackdate)),
cb.lessThanOrEqualTo(
testEventJoin.get(BaseTestInfo_.dateTestedBackdate), endDate)),
cb.and(
cb.isNull(testEventJoin.get(BaseTestInfo_.dateTestedBackdate)),
cb.lessThanOrEqualTo(
testEventJoin.get(AuditedEntity_.createdAt), endDate))));
}

return p;
};
}

@Transactional(readOnly = true)
@AuthorizationConfiguration.RequirePermissionViewAllFacilityResults
public Page<Result> getOrganizationResults(
UUID patientId,
TestResult result,
PersonRole role,
SupportedDisease supportedDisease,
Date startDate,
Date endDate,
int pageOffset,
int pageSize) {

PageRequest pageRequest =
PageRequest.of(pageOffset, pageSize, Sort.by("createdAt").descending());

return resultRepository.findAll(
buildResultSearchFilter(
null, patientId, result, role, supportedDisease, startDate, endDate),
pageRequest);
}

@Transactional(readOnly = true)
@AuthorizationConfiguration.RequirePermissionReadResultListAtFacility
public Page<Result> getFacilityResults(
UUID facilityId,
UUID patientId,
TestResult result,
PersonRole role,
SupportedDisease supportedDisease,
Date startDate,
Date endDate,
int pageOffset,
int pageSize) {

PageRequest pageRequest =
PageRequest.of(pageOffset, pageSize, Sort.by("createdAt").descending());

return resultRepository.findAll(
buildResultSearchFilter(
facilityId, patientId, result, role, supportedDisease, startDate, endDate),
pageRequest);
}

public TestEvent addResultsToTestEvent(TestEvent testEvent, Collection<Result> results) {
if (testEvent == null || results == null || results.isEmpty()) {
Expand Down
37 changes: 35 additions & 2 deletions backend/src/main/resources/graphql/main.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ type TestOrder {
id: ID!
internalId: ID! @deprecated(reason: "alias for 'id'")
patient: Patient!
facility: Facility!
dateAdded: String!
dateUpdated: DateTime
pregnancy: String
noSymptoms: Boolean
symptoms: String
Expand Down Expand Up @@ -290,9 +292,29 @@ type TestResultsPage {
content: [TestResult]
}

type Result {
id: ID!
facility: Facility!
patient: Patient!
dateAdded: String!
dateUpdated: DateTime
deviceType: DeviceType!
disease: String!
testResult: String!
dateTested: DateTime!
correctionStatus: String
reasonForCorrection: String
createdBy: ApiUser
}

type ResultsPage {
totalElements: Int
content: [Result]
}

type MultiplexResult {
disease: SupportedDisease
testResult: String
disease: SupportedDisease!
testResult: String!
}

input MultiplexResultInput {
Expand Down Expand Up @@ -468,6 +490,17 @@ type Query {
): Int @requiredPermissions(allOf: ["READ_RESULT_LIST"])
testResult(id: ID!): TestResult
@requiredPermissions(allOf: ["READ_RESULT_LIST"])
resultsPage(
facilityId: ID
patientId: ID
result: String
role: String
disease: String
startDate: DateTime
endDate: DateTime
pageNumber: Int = 0
pageSize: Int = 5000
): ResultsPage @requiredPermissions(allOf: ["READ_RESULT_LIST"])
organizationLevelDashboardMetrics(
startDate: DateTime!
endDate: DateTime!
Expand Down
Loading

0 comments on commit 8353d11

Please sign in to comment.