Skip to content
This repository has been archived by the owner on Dec 18, 2024. It is now read-only.

MAT-6193: QDM Test Case Coverage Tabs and Accordion #498

Merged
merged 20 commits into from
Nov 29, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,14 @@ define function denomObs(Encounter "Encounter, Performed"):
define function numerObs(Encounter "Encounter, Performed"):
duration in days of Encounter.relevantPeriod

define "ipp":
exists ["Encounter, Performed"] E

define "denom":
exists ["Encounter, Performed"] E

define "num":
exists ["Encounter, Performed"] E

define "IP2":
exists ["Encounter, Performed"] E`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
interface Props {
population: string;
populationText: any;
}
import { Accordion } from "@madie/madie-design-system/dist/react";

const CoverageTab = ({ population, populationText }: Props) => {
return (
<Accordion title={population} isOpen={true}>
<pre>{populationText.text}</pre>
</Accordion>
);
};

export default CoverageTab;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import CoverageTab from "./CoverageTab";

import { MappedCql } from "../../../../../util/GroupCoverageHelpers";
import "twin.macro";
import "styled-components/macro";
interface Props {
populationCriteria: any;
mappedCql: MappedCql;
}

const CoverageTabList = ({ populationCriteria, mappedCql }: Props) => {
return (
<div data-testid="coverage-tab-list">
{mappedCql &&
populationCriteria.length &&
populationCriteria.map((pop, i) => {
return (
<CoverageTab
key={i}
population={pop.name}
populationText={mappedCql[pop.name]}
/>
);
})}
<div tw="flex mt-5" key={"1"}>
<div tw="flex-none w-1/5"></div>
<div></div>
</div>
</div>
);
};

export default CoverageTabList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import { mapCoverageCql } from "../../../../util/GroupCoverageHelpers";
import "twin.macro";
import "styled-components/macro";
import CoverageTabList from "./CoverageTabs/CoverageTabList";

const TestCaseCoverage = ({ populationCriteria, measureCql }) => {
return (
<div tw="p-5" style={{ paddingRight: ".25rem" }}>
<CoverageTabList
data-testid="coverage-tab-list"
populationCriteria={
populationCriteria?.populations.filter((pop) => pop.definition) || {}
}
mappedCql={mapCoverageCql(measureCql, populationCriteria)}
/>
</div>
);
};

export default TestCaseCoverage;
48 changes: 37 additions & 11 deletions src/components/testCaseLanding/qdm/TestCaseList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { ValueSet } from "cqm-models";
import qdmCalculationService, {
QdmCalculationService,
} from "../../../api/QdmCalculationService";
import { measureCql } from "../../editTestCase/groupCoverage/_mocks_/QdmMeasureCql";

const serviceConfig: ServiceConfig = {
elmTranslationService: { baseUrl: "translator.url" },
Expand All @@ -55,13 +56,31 @@ const serviceConfig: ServiceConfig = {
baseUrl: "http.com",
},
};

const mappedCql = {
initialPopulation: {
id: "0223850b-df35-4d0d-98f1-df993f7de328",
text: 'define "Initial Population":\r\n ["Encounter, Performed": "Emergency Department Visit"]',
},
denominator: {
id: "91ae58e2-aa5a-4163-ad39-835ff7d0822b",
text: 'define "IP2":\r\n ["Encounter, Performed"] E',
},
numerator: {
id: "dc788738-8c66-4538-990c-7a57430c55e3",
text: 'define "IP2":\r\n ["Encounter, Performed"] E',
},
};
const MEASURE_CREATEDBY = "testuser";
// Mock data for Measure retrieved from MeasureService
const measure = {
id: "1",
measureName: "measureName",
createdBy: MEASURE_CREATEDBY,
cqlLibraryName: "testLibrary",
cmsId: "1234",
measureSetId: "1234",

scoring: MeasureScoring.PROPORTION,
groups: [
{
id: "1",
Expand All @@ -88,6 +107,7 @@ const measure = {
],
model: Model.QDM_5_6,
acls: [{ userId: "[email protected]", roles: ["SHARED_WITH"] }],
cql: measureCql,
} as unknown as Measure;

jest.mock("@madie/madie-util", () => ({
Expand Down Expand Up @@ -487,7 +507,7 @@ const useMeasureServiceMockResolved = {

const getAccessToken = jest.fn();
let cqmConversionService = new CqmConversionService("url", getAccessToken);
const cqmMeasure = cqmConversionService.convertToCqmMeasure(measure);
const cqmMeasure = {};
const valueSets = [] as ValueSet[];
const setMeasure = jest.fn();
const setCqmMeasure = jest.fn();
Expand Down Expand Up @@ -732,15 +752,24 @@ describe("TestCaseList component", () => {
});

userEvent.click(screen.getByTestId("coverage-tab"));
await waitFor(() => {
expect(
screen.getByTestId("code-coverage-highlighting")
).toBeInTheDocument();
});
userEvent.click(screen.getByTestId("passing-tab"));
expect(screen.getByTestId("test-case-tbl")).toBeInTheDocument();
});

it("accordions for cql parts", async () => {
measure.createdBy = MEASURE_CREATEDBY;
renderTestCaseListComponent();
const table = await screen.findByTestId("test-case-tbl");

userEvent.click(screen.getByTestId("coverage-tab"));
const coverageTabList = await screen.findByTestId("coverage-tab-list");
expect(coverageTabList).toBeInTheDocument();
const allAccordions = await screen.findAllByTestId("accordion");
expect(allAccordions[0]).toBeInTheDocument();
const firstAccordion = await screen.queryByText("initialPopulation");
expect(firstAccordion).toBeInTheDocument();
});

it("Run Test Cases button should be disabled if no valid test cases", async () => {
measure.createdBy = MEASURE_CREATEDBY;
testCases[0].validResource = false;
Expand Down Expand Up @@ -877,9 +906,6 @@ describe("TestCaseList component", () => {

it("should render New Test Case button and navigate to the Create New Test Case page when button clicked", async () => {
const { getByTestId } = renderTestCaseListComponent();

const codeCoverageTabs = await screen.findByTestId("code-coverage-tabs");
expect(codeCoverageTabs).toBeInTheDocument();
const passingTab = await screen.findByTestId("passing-tab");
expect(passingTab).toBeInTheDocument();
const testCaseList = await screen.findByTestId("test-case-tbl");
Expand Down Expand Up @@ -954,7 +980,7 @@ describe("TestCaseList component", () => {
});

userEvent.click(executeButton);
await waitFor(() => expect(screen.getByText("50%")).toBeInTheDocument());
await waitFor(() => expect(screen.getByText("0%")).toBeInTheDocument());

const table = await screen.findByTestId("test-case-tbl");
const tableRows = table.querySelectorAll("tbody tr");
Expand Down
32 changes: 13 additions & 19 deletions src/components/testCaseLanding/qdm/TestCaseList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import calculationService from "../../../api/CalculationService";
import { DetailedPopulationGroupResult } from "fqm-execution/build/types/Calculator";
import { checkUserCanEdit, measureStore } from "@madie/madie-util";
import CreateCodeCoverageNavTabs from "./CreateCodeCoverageNavTabs";
import CodeCoverageHighlighting from "../common/CodeCoverageHighlighting";
import CreateNewTestCaseDialog from "../../createTestCase/CreateNewTestCaseDialog";
import { MadieSpinner, Toast } from "@madie/madie-design-system/dist/react";
import TestCaseListSideBarNav from "../common/TestCaseListSideBarNav";
Expand All @@ -29,6 +28,8 @@ import qdmCalculationService, {
CqmExecutionResultsByPatient,
} from "../../../api/QdmCalculationService";
import TestCaseImportFromBonnieDialogQDM from "../common/import/TestCaseImportFromBonnieDialogQDM";
import TestCaseCoverage from "./TestCaseCoverage/TestCaseCoverage";
import CodeCoverageHighlighting from "../common/CodeCoverageHighlighting";
import { QDMPatient, DataElement } from "cqm-models";

export const IMPORT_ERROR =
Expand Down Expand Up @@ -61,7 +62,7 @@ export const getCoverageValueFromHtml = (
};

const TestCaseList = (props: TestCaseListProps) => {
const { setErrors } = props;
const { setErrors, errors } = props;
const { measureId } = useParams<{ measureId: string }>();
const {
testCases,
Expand Down Expand Up @@ -176,16 +177,6 @@ const TestCaseList = (props: TestCaseListProps) => {
useEffect(() => {
const validTestCases = testCases?.filter((tc) => tc.validResource);
if (validTestCases && calculationOutput) {
// Pull Clause Coverage from coverage HTML
setCoveragePercentage(
getCoverageValueFromHtml(
calculationOutput["groupClauseCoverageHTML"],
selectedPopCriteria.id
)
);
setCoverageHTML(
removeHtmlCoverageHeader(calculationOutput["groupClauseCoverageHTML"])
);
const executionResults: CqmExecutionResultsByPatient = calculationOutput;

validTestCases.forEach((testCase) => {
Expand Down Expand Up @@ -247,7 +238,6 @@ const TestCaseList = (props: TestCaseListProps) => {
return null;
}
const validTestCases = testCases?.filter((tc) => tc.validResource);

if (validTestCases && validTestCases.length > 0 && cqmMeasure) {
setExecuting(true);
try {
Expand All @@ -258,7 +248,6 @@ const TestCaseList = (props: TestCaseListProps) => {
cqmMeasure,
patients
);

setCalculationOutput(calculationOutput);
} catch (error) {
console.error("calculateTestCases: error.message = " + error.message);
Expand Down Expand Up @@ -403,11 +392,16 @@ const TestCaseList = (props: TestCaseListProps) => {
</div>
</div>
)}

{activeTab === "coverage" && coverageHTML && (
<CodeCoverageHighlighting
coverageHTML={coverageHTML[selectedPopCriteria.id]}
/>
{activeTab === "coverage" && (
<div tw="overflow-x-auto sm:-mx-6 lg:-mx-8">
<div tw="py-2 inline-block min-w-full sm:px-6 lg:px-8">
<TestCaseCoverage
data-testid="test-case-coverage"
populationCriteria={selectedPopCriteria}
measureCql={measure.cql}
/>
</div>
</div>
)}
</div>
</>
Expand Down
20 changes: 20 additions & 0 deletions src/util/GroupCoverageHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,23 @@ export const mapCql = (measureCql: string, measureGroups): MappedCql => {
}, {});
}
};

export const mapCoverageCql = (measureCql: string, groupPopulations) => {
ethankaplan marked this conversation as resolved.
Show resolved Hide resolved
const filteredPopulations = groupPopulations.populations.filter(
(population) => population.definition
);
const result = { ...groupPopulations, populations: filteredPopulations };
if (measureCql && result) {
const definitions = new CqlAntlr(measureCql).parse().expressionDefinitions;
return result.populations.reduce((acc, population) => {
const matchingDef = definitions.find(
(def) => def.name.replace(/"/g, "") === population.definition
);
if (matchingDef) {
acc[population.name] = { id: population.id, text: matchingDef.text };
}

return acc;
}, {});
}
};
Loading