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

Commit

Permalink
Merge pull request #500 from MeasureAuthoringTool/feature/mat-6075-de…
Browse files Browse the repository at this point in the history
…finitions-used-section-qicore

[MAT-6075] Display CQL Definition Callstack
  • Loading branch information
jkotanchik-SB authored Dec 7, 2023
2 parents 770eef9 + 7418b11 commit 0797229
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm exec pretty-quick --staged && npm exec concurrently npm:test npm:lint
npm exec pretty-quick --staged && npm exec concurrently "npm:lint" "npm:test -- --silent"
39 changes: 39 additions & 0 deletions src/api/useCqlParsingService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import axios from "axios";
import useServiceConfig from "./useServiceConfig";
import { ServiceConfig } from "./ServiceContext";
import { useOktaTokens } from "@madie/madie-util";
import { CqlDefinitionCallstack } from "../components/editTestCase/groupCoverage/QiCoreGroupCoverage";

export class CqlParsingService {
constructor(private baseUrl: string, private getAccessToken: () => string) {}

async getDefinitionCallstacks(cql: string): Promise<CqlDefinitionCallstack> {
try {
const response = await axios.put<string>(
`${this.baseUrl}/cql/callstacks`,
cql,
{
headers: {
Authorization: `Bearer ${this.getAccessToken()}`,
"Content-Type": "text/plain",
},
}
);
return response.data as unknown as CqlDefinitionCallstack;
} catch (err) {
const message = `Unable to retrieve used definition references`;
throw new Error(message);
}
}
}

const useCqlParsingService = (): CqlParsingService => {
const serviceConfig: ServiceConfig = useServiceConfig();
const { getAccessToken } = useOktaTokens();
return new CqlParsingService(
serviceConfig?.elmTranslationService.baseUrl,
getAccessToken
);
};

export default useCqlParsingService;
96 changes: 90 additions & 6 deletions src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,39 @@ import {
} from "../../../util/GroupCoverageHelpers";
import "./QiCoreGroupCoverage.scss";

export interface CqlDefinitionExpression {
id?: string;
definitionName: string;
definitionLogic: string;
context: string;
supplDataElement: boolean;
popDefinition: boolean;
commentString: string;
returnType: string | null;
parentLibrary: string | null;
libraryDisplayName: string | null;
libraryVersion: string | null;
function: boolean;
name: string;
logic: string;
}

export interface CqlDefinitionCallstack {
[key: string]: Array<CqlDefinitionExpression>;
}

interface Props {
groupPopulations: GroupPopulation[];
mappedCalculationResults: MappedCalculationResults;
cqlDefinitionCallstack?: CqlDefinitionCallstack;
}

interface Statement {
isFunction: boolean;
relevance: Relevance;
statementLevelHTML?: string | undefined;
pretty?: string;
name?: string;
}

interface PopulationStatement extends Statement {
Expand All @@ -50,6 +73,7 @@ const allDefinitions = [
const QiCoreGroupCoverage = ({
groupPopulations,
mappedCalculationResults,
cqlDefinitionCallstack,
}: Props) => {
// selected group/criteria
const [selectedCriteria, setSelectedCriteria] = useState<string>("");
Expand Down Expand Up @@ -115,6 +139,7 @@ const QiCoreGroupCoverage = ({
populationName:
FHIR_POPULATION_CODES[relevantPopulations[key].populationType],
id: relevantPopulations[key].populationId,
name: key,
};
return output;
}, {});
Expand Down Expand Up @@ -224,6 +249,39 @@ const QiCoreGroupCoverage = ({
}
};

const generateCallstackText = (selectedDefinition: Statement): string => {
let text = "";
cqlDefinitionCallstack[selectedDefinition.name]?.forEach(
(calledDefinition) => {
// Get Highlighted HTML from execution results
text +=
mappedCalculationResults[selectedCriteria]["statementResults"][
calledDefinition.name
].statementLevelHTML;
// Get the callstack for each definition called by the parent statement
getCallstack(calledDefinition.id).forEach((name) => {
text +=
mappedCalculationResults[selectedCriteria]["statementResults"][name]
.statementLevelHTML;
});
}
);
return text;
};

const getCallstack = (defId: string): string[] => {
let calledDefinitions: string[] = [];
cqlDefinitionCallstack[defId]?.forEach((calledDefinition) => {
calledDefinitions.push(calledDefinition.name);
if (cqlDefinitionCallstack[calledDefinition.id]) {
calledDefinitions = calledDefinitions.concat(
getCallstack(calledDefinition.id)
);
}
});
return calledDefinitions;
};

return (
<>
<div tw="border-b pb-2">
Expand Down Expand Up @@ -300,12 +358,38 @@ const QiCoreGroupCoverage = ({
data-testid={`${selectedHighlightingTab.abbreviation}-highlighting`}
>
{selectedPopulationDefinitionResults ? (
<div>
{parse(selectedPopulationDefinitionResults?.statementLevelHTML)}
<GroupCoverageResultsSection
results={selectedPopulationDefinitionResults.pretty}
/>
</div>
<>
<div>
{parse(
selectedPopulationDefinitionResults?.statementLevelHTML
)}
<GroupCoverageResultsSection
results={selectedPopulationDefinitionResults.pretty}
/>
</div>
<div
style={{
fontFamily: "Rubik",
fontSize: "14px",
fontWeight: "500",
color: "#0073C8",
}}
>
Definition(s) Used
</div>
<div
style={{
fontFamily: "Rubik",
fontSize: "12px",
fontWeight: "500",
whiteSpace: "pre-wrap",
}}
>
{parse(
generateCallstackText(selectedPopulationDefinitionResults)
)}
</div>
</>
) : (
"No results available"
)}
Expand Down
1 change: 1 addition & 0 deletions src/components/editTestCase/qiCore/EditTestCase.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { TestCaseValidator } from "../../../validators/TestCaseValidator";
import { checkUserCanEdit } from "@madie/madie-util";
import { PopulationType as FqmPopulationType } from "fqm-execution/build/types/Enums";
import { addValues } from "../../../util/DefaultValueProcessor";
import { defineElm } from "../../../__mocks__/define-elm-fixture";

//temporary solution (after jest updated to version 27) for error: thrown: "Exceeded timeout of 5000 ms for a test.
jest.setTimeout(60000);
Expand Down
16 changes: 16 additions & 0 deletions src/components/editTestCase/qiCore/EditTestCase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import { Bundle } from "fhir/r4";
import { Allotment } from "allotment";
import ElementsTab from "./LeftPanel/ElementsTab/ElementsTab";
import { QiCoreResourceProvider } from "../../../util/QiCorePatientProvider";
import useCqlParsingService from "../../../api/useCqlParsingService";
import { CqlDefinitionCallstack } from "../groupCoverage/QiCoreGroupCoverage";

const TestCaseForm = tw.form`m-3`;
const ValidationErrorsButton = tw.button`
Expand Down Expand Up @@ -205,6 +207,7 @@ const EditTestCase = (props: EditTestCaseProps) => {
// Avoid infinite dependency render. May require additional error handling for timeouts.
const testCaseService = useRef(useTestCaseServiceApi());
const calculation = useRef(calculationService());
const cqlParsingService = useRef(useCqlParsingService());
const [alert, setAlert] = useState<AlertProps>(null);
const { errors, setErrors } = props;
if (!errors) {
Expand Down Expand Up @@ -261,6 +264,7 @@ const EditTestCase = (props: EditTestCaseProps) => {
const [groupPopulations, setGroupPopulations] = useState<GroupPopulation[]>(
[]
);
const [callstackMap, setCallstackMap] = useState<CqlDefinitionCallstack>();

const {
measureState,
Expand Down Expand Up @@ -415,6 +419,17 @@ const EditTestCase = (props: EditTestCaseProps) => {
load.current = +1;
loadTestCase();
}

if (_.isNil(callstackMap) && measure?.cql) {
cqlParsingService.current
.getDefinitionCallstacks(measure.cql)
.then((callstack: CqlDefinitionCallstack) => {
setCallstackMap(callstack);
})
.catch((error) => {
console.error(error);
});
}
}, [
id,
measureId,
Expand Down Expand Up @@ -886,6 +901,7 @@ const EditTestCase = (props: EditTestCaseProps) => {
calculationResults={populationGroupResults}
calculationErrors={calculationErrors}
groupPopulations={groupPopulations}
cqlDefinitionCallstack={callstackMap}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { DetailedPopulationGroupResult } from "fqm-execution/build/types/Calcula
import { MadieAlert } from "@madie/madie-design-system/dist/react";
import { GroupPopulation, PopulationType } from "@madie/madie-models";
import { useFeatureFlags } from "@madie/madie-util";
import QiCoreGroupCoverage from "../../groupCoverage/QiCoreGroupCoverage";
import { Relevance } from "fqm-execution";
import QiCoreGroupCoverage, {
CqlDefinitionCallstack,
} from "../../groupCoverage/QiCoreGroupCoverage";

type ErrorProps = {
status?: "success" | "warning" | "error" | "info" | "meta";
Expand All @@ -19,6 +21,7 @@ type CalculationResultType = {
calculationResults: DetailedPopulationGroupResult[];
calculationErrors: ErrorProps;
groupPopulations: GroupPopulation[];
cqlDefinitionCallstack?: CqlDefinitionCallstack;
};

export interface MappedCalculationResults {
Expand All @@ -45,6 +48,7 @@ const CalculationResults = ({
calculationResults,
calculationErrors,
groupPopulations,
cqlDefinitionCallstack = {},
}: CalculationResultType) => {
// template for group name coming from execution engine
const originalGroupName = (name) => {
Expand Down Expand Up @@ -140,6 +144,7 @@ const CalculationResults = ({
<QiCoreGroupCoverage
groupPopulations={groupPopulations}
mappedCalculationResults={mapCalculationResults(calculationResults)}
cqlDefinitionCallstack={cqlDefinitionCallstack}
/>
)}
{!featureFlags.highlightingTabs && coverageHtmls && (
Expand Down

0 comments on commit 0797229

Please sign in to comment.