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

[MAT-6075] Display CQL Definition Callstack #500

Merged
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 .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);

Check warning on line 25 in src/api/useCqlParsingService.ts

View check run for this annotation

Codecov / codecov/patch

src/api/useCqlParsingService.ts#L24-L25

Added lines #L24 - L25 were not covered by tests
}
}
}

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

export default useCqlParsingService;
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,39 @@
} 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 QiCoreGroupCoverage = ({
groupPopulations,
mappedCalculationResults,
cqlDefinitionCallstack,
}: Props) => {
// selected group/criteria
const [selectedCriteria, setSelectedCriteria] = useState<string>("");
Expand Down Expand Up @@ -115,6 +139,7 @@
populationName:
FHIR_POPULATION_CODES[relevantPopulations[key].populationType],
id: relevantPopulations[key].populationId,
name: key,
};
return output;
}, {});
Expand Down Expand Up @@ -224,6 +249,39 @@
}
};

const generateCallstackText = (selectedDefinition: Statement): string => {
let text = "";
cqlDefinitionCallstack[selectedDefinition.name]?.forEach(
(calledDefinition) => {
// Get Highlighted HTML from execution results
text +=

Check warning on line 257 in src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx#L257

Added line #L257 was not covered by tests
mappedCalculationResults[selectedCriteria]["statementResults"][
calledDefinition.name
].statementLevelHTML;
// Get the callstack for each definition called by the parent statement
getCallstack(calledDefinition.id).forEach((name) => {
text +=

Check warning on line 263 in src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx#L262-L263

Added lines #L262 - L263 were not covered by tests
mappedCalculationResults[selectedCriteria]["statementResults"][name]
.statementLevelHTML;
});
}
);
return text;
};

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

Check warning on line 275 in src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx#L273-L275

Added lines #L273 - L275 were not covered by tests
if (cqlDefinitionCallstack[calledDefinition.id]) {
calledDefinitions = calledDefinitions.concat(

Check warning on line 277 in src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx#L277

Added line #L277 was not covered by tests
getCallstack(calledDefinition.id)
);
}
});
return calledDefinitions;

Check warning on line 282 in src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/groupCoverage/QiCoreGroupCoverage.tsx#L282

Added line #L282 was not covered by tests
};

return (
<>
<div tw="border-b pb-2">
Expand Down Expand Up @@ -300,12 +358,38 @@
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 { 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 @@
// 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 [groupPopulations, setGroupPopulations] = useState<GroupPopulation[]>(
[]
);
const [callstackMap, setCallstackMap] = useState<CqlDefinitionCallstack>();

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

if (_.isNil(callstackMap) && measure?.cql) {
cqlParsingService.current
.getDefinitionCallstacks(measure.cql)
.then((callstack: CqlDefinitionCallstack) => {
setCallstackMap(callstack);
})
.catch((error) => {
console.error(error);

Check warning on line 430 in src/components/editTestCase/qiCore/EditTestCase.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/editTestCase/qiCore/EditTestCase.tsx#L430

Added line #L430 was not covered by tests
});
}
}, [
id,
measureId,
Expand Down Expand Up @@ -886,6 +901,7 @@
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
Loading