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 12 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
@@ -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, { useEffect, useState } from "react";
import _ from "lodash";
import CoverageTab from "./CoverageTab";

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

const CoverageTabList = ({ groupPopulations, mappedCql }: Props) => {
return (
<div data-testid="coverage-tab-list">
{mappedCql &&
groupPopulations.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 = ({ groupPopulations, measureCql }) => {
return (
<div tw="p-5" style={{ paddingRight: ".25rem" }}>
<CoverageTabList
data-testid="coverage-tab-list"
groupPopulations={groupPopulations.populations.filter(
ethankaplan marked this conversation as resolved.
Show resolved Hide resolved
(pop) => pop.definition
)}
mappedCql={mapCoverageCql(measureCql, groupPopulations)}
/>
</div>
);
};

export default TestCaseCoverage;
52 changes: 41 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 All @@ -102,6 +122,9 @@ jest.mock("@madie/madie-util", () => ({
})),
}));

jest.mock("../../../util/GroupCoverageHelpers", () => ({
mapCoverageCql: jest.fn().mockImplementation(() => mappedCql),
}));
let importingTestCases = [];
jest.mock(
"../common/import/TestCaseImportFromBonnieDialogQDM",
Expand Down Expand Up @@ -486,7 +509,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 @@ -731,15 +754,25 @@ 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 @@ -876,9 +909,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 @@ -953,7 +983,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 @@ -9,7 +9,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 @@ -26,6 +25,8 @@ import qdmCalculationService, {
CqmExecutionResultsByPatient,
} from "../../../api/QdmCalculationService";
import TestCaseImportFromBonnieDialogQDM from "../common/import/TestCaseImportFromBonnieDialogQDM";
import TestCaseCoverage from "./TestCaseCoverage/TestCaseCoverage";
import CodeCoverageHighlighting from "../common/CodeCoverageHighlighting";

export const IMPORT_ERROR =
"An error occurred while importing your test cases. Please try again, or reach out to the Help Desk.";
Expand Down Expand Up @@ -57,7 +58,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 @@ -172,16 +173,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 @@ -243,7 +234,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 @@ -254,7 +244,6 @@ const TestCaseList = (props: TestCaseListProps) => {
cqmMeasure,
patients
);

setCalculationOutput(calculationOutput);
} catch (error) {
console.error("calculateTestCases: error.message = " + error.message);
Expand Down Expand Up @@ -406,11 +395,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"
groupPopulations={selectedPopCriteria}
ethankaplan marked this conversation as resolved.
Show resolved Hide resolved
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 mapCoverageCql = (measureCql: string, groupPopulations) => {
ethankaplan marked this conversation as resolved.
Show resolved Hide resolved
const filteredPopulations = groupPopulations.populations.filter(
(population) => population.definition

Check warning on line 75 in src/util/GroupCoverageHelpers.ts

View check run for this annotation

Codecov / codecov/patch

src/util/GroupCoverageHelpers.ts#L74-L75

Added lines #L74 - L75 were not covered by tests
);
const result = { ...groupPopulations, populations: filteredPopulations };

Check warning on line 77 in src/util/GroupCoverageHelpers.ts

View check run for this annotation

Codecov / codecov/patch

src/util/GroupCoverageHelpers.ts#L77

Added line #L77 was not covered by tests
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

Check warning on line 82 in src/util/GroupCoverageHelpers.ts

View check run for this annotation

Codecov / codecov/patch

src/util/GroupCoverageHelpers.ts#L79-L82

Added lines #L79 - L82 were not covered by tests
);
if (matchingDef) {
acc[population.name] = { id: population.id, text: matchingDef.text };

Check warning on line 85 in src/util/GroupCoverageHelpers.ts

View check run for this annotation

Codecov / codecov/patch

src/util/GroupCoverageHelpers.ts#L85

Added line #L85 was not covered by tests
}

return acc;

Check warning on line 88 in src/util/GroupCoverageHelpers.ts

View check run for this annotation

Codecov / codecov/patch

src/util/GroupCoverageHelpers.ts#L88

Added line #L88 was not covered by tests
}, {});
}
};
Loading