Skip to content

Commit 0dc91f4

Browse files
committed
Real submit data option
1 parent 398d769 commit 0dc91f4

1 file changed

Lines changed: 105 additions & 35 deletions

File tree

components/calculation/PopulationCalculation.tsx

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
Drawer,
66
Grid,
77
Group,
8+
Menu,
89
Modal,
910
Radio,
1011
Select,
1112
Space,
1213
Text,
13-
Tooltip
14+
Tooltip,
15+
useMantineTheme
1416
} from '@mantine/core';
1517
import CodeMirror from '@uiw/react-codemirror';
1618
import { useRecoilValue } from 'recoil';
@@ -20,7 +22,14 @@ import { measureBundleState } from '../../state/atoms/measureBundle';
2022
import { useState } from 'react';
2123
import { measurementPeriodFormattedState } from '../../state/atoms/measurementPeriod';
2224
import { showNotification } from '@mantine/notifications';
23-
import { IconAlertCircle, IconCircleCheck, IconCopy } from '@tabler/icons';
25+
import {
26+
IconAlertCircle,
27+
IconChevronDown,
28+
IconCircleCheck,
29+
IconCopy,
30+
IconPackage,
31+
IconSquareCheck
32+
} from '@tabler/icons';
2433
import { getPatientInfoString, getPatientNameString } from '../../util/fhir/patient';
2534
import { createDataExchangeMeasureReport, createPatientBundle } from '../../util/fhir/resourceCreation';
2635
import PopulationResultTable, { LabeledDetailedResult } from './PopulationResultsTable';
@@ -53,6 +62,7 @@ export default function PopulationCalculation() {
5362
const [subjectValue, setSubjectValue] = useState<string | null>('');
5463
const [subjectData, setSubjectData] = useState<{ value: string; label: string }[]>([]);
5564
const [evaluateText, setEvaluateText] = useState<string>('');
65+
const theme = useMantineTheme();
5666

5767
/**
5868
* Creates object that maps patient ids to their name/DOB info strings.
@@ -71,58 +81,102 @@ export default function PopulationCalculation() {
7181
* without using Measure/$deqm-submit-data endpoint
7282
* Each transaction bundle should contain DEQM Data Exchange MeasureReports with data-of-interest
7383
* and should be for a single subject (will do a separate POST for each patient)
84+
* @param proto: true if we should do a proto submit data that simply POSTs transaction bundles
7485
* @returns { {postedId: string, testCaseInfo: testCaseInfo}|undefined[] } objects with the evaluation service ids for POSTed patients (may be different than sent IDs or undefined if send was unsuccessful) and the corresponding test case information
7586
*/
76-
const submitDataToEvaluationService = async (): Promise<
77-
({ postedId: string; testCaseInfo: TestCaseInfo } | undefined)[]
78-
> => {
79-
// collect data and POST to evaluation service (TODO: should be $submit-data when available)
80-
87+
const submitDataToEvaluationService = async (
88+
proto: boolean
89+
): Promise<({ postedId: string; testCaseInfo: TestCaseInfo } | undefined)[]> => {
8190
const measure = measureBundle.content?.entry?.find(e => e.resource?.resourceType === 'Measure')
8291
?.resource as fhir4.Measure;
8392

84-
const postedIds: Promise<{ postedId: string; testCaseInfo: TestCaseInfo } | undefined>[] = Object.keys(
85-
currentPatients
86-
).map(async id => {
87-
const bundle = createPatientBundle(
93+
const patientIds = Object.keys(currentPatients);
94+
const bundles = patientIds.map(id => {
95+
return createPatientBundle(
8896
currentPatients[id].patient,
8997
minimizeTestCaseResources(currentPatients[id], measureBundle.content, drLookupByType),
9098
currentPatients[id].fullUrl,
9199
createDataExchangeMeasureReport(measure, measurementPeriodFormatted as fhir4.Period, id)
92100
);
93-
const response = await fetch(`${evaluationServiceUrl}/`, {
101+
});
102+
103+
let responseBundles: (fhir4.Bundle | undefined)[] = [];
104+
if (proto) {
105+
// Simple POST
106+
const bundlePromises = bundles.map(async (bundle, idx) => {
107+
const response = await fetch(`${evaluationServiceUrl}/`, {
108+
method: 'POST',
109+
body: JSON.stringify(bundle),
110+
headers: { 'Content-Type': 'application/json+fhir' }
111+
});
112+
const responseBody: fhir4.Bundle | fhir4.OperationOutcome = await response.json();
113+
if (responseBody.resourceType === 'OperationOutcome') {
114+
showNotification({
115+
icon: <IconAlertCircle />,
116+
title: 'Patient data submission failed',
117+
message: `Submitting data for Patient: ${getPatientNameString(
118+
currentPatients[patientIds[idx]].patient
119+
)} failed with details: "${responseBody.issue[0].details?.text ?? response.status}"`,
120+
color: 'red'
121+
});
122+
return undefined;
123+
}
124+
return responseBody;
125+
});
126+
responseBundles = await Promise.all(bundlePromises);
127+
} else {
128+
// full $submit-data
129+
const parameters = {
130+
resourceType: 'Parameters',
131+
parameter: bundles.map(bundle => {
132+
return {
133+
name: 'bundle',
134+
resource: bundle
135+
};
136+
})
137+
};
138+
const response = await fetch(`${evaluationServiceUrl}/Measure/$submit-data`, {
94139
method: 'POST',
95-
body: JSON.stringify(bundle),
140+
body: JSON.stringify(parameters),
96141
headers: { 'Content-Type': 'application/json+fhir' }
97142
});
98-
const responseBody: fhir4.Bundle | fhir4.OperationOutcome = await response.json();
143+
const responseBody: fhir4.Parameters | fhir4.OperationOutcome = await response.json();
99144
if (responseBody.resourceType === 'OperationOutcome') {
100145
showNotification({
101146
icon: <IconAlertCircle />,
102-
title: 'Patient data submission failed',
103-
message: `Submitting data for Patient: ${getPatientNameString(
104-
currentPatients[id].patient
105-
)} failed with details: "${responseBody.issue[0].details?.text ?? response.status}"`,
147+
title: '$submit-data failure',
148+
message: `$submit-data operation failed with details: "${
149+
responseBody.issue[0].details?.text ?? response.status
150+
}"`,
106151
color: 'red'
107152
});
108-
return;
153+
} else if (!responseBody.parameter) {
154+
showNotification({
155+
icon: <IconAlertCircle />,
156+
title: '$submit-data no parameters',
157+
message: `$submit-data operation received a response with no parameters`,
158+
color: 'red'
159+
});
160+
} else {
161+
responseBundles = responseBody.parameter.map(p => p.resource as fhir4.Bundle);
109162
}
163+
}
110164

111-
// should return transaction response bundles from which we can pull the posted patient id
112-
const locationId = responseBody.entry
165+
const postedIds = responseBundles.map((bundle, idx) => {
166+
// should be transaction response bundles from which we can pull the posted patient id
167+
const locationId = bundle?.entry
113168
?.find(e => e.response?.location?.includes('Patient/'))
114169
?.response?.location?.split('Patient/')[1];
115-
return locationId ? { postedId: locationId, testCaseInfo: currentPatients[id] } : undefined;
170+
return locationId ? { postedId: locationId, testCaseInfo: currentPatients[patientIds[idx]] } : undefined;
116171
});
117-
118-
return Promise.all(postedIds);
172+
return postedIds;
119173
};
120174

121175
/**
122176
* Wrapper function that calls submitDataToEvaluationService() and resolves patient data for future evaluation
123177
*/
124-
const submitData = () => {
125-
submitDataToEvaluationService()
178+
const submitData = (proto = false) => {
179+
submitDataToEvaluationService(proto)
126180
.then(postedIds => {
127181
const resolvedIds = postedIds?.filter(idObj => idObj !== undefined);
128182
if (resolvedIds.length > 0) {
@@ -367,15 +421,31 @@ export default function PopulationCalculation() {
367421
&nbsp;Show Clause Coverage
368422
</Button>
369423
</Tooltip>
370-
<Button
371-
data-testid="submit-data-button"
372-
aria-label="Submit Data"
373-
styles={{ root: { marginTop: 20 } }}
374-
onClick={() => submitData()}
375-
variant="outline"
376-
>
377-
&nbsp;Submit Data
378-
</Button>
424+
<Menu>
425+
<Menu.Target>
426+
<Button
427+
styles={{ root: { marginTop: 20 } }}
428+
variant="outline"
429+
rightIcon={<IconChevronDown size={18} />}
430+
>
431+
Submit Data
432+
</Button>
433+
</Menu.Target>
434+
<Menu.Dropdown>
435+
<Menu.Item
436+
onClick={() => submitData()}
437+
icon={<IconPackage color={theme.colors.green[6]} stroke={1.5} />}
438+
>
439+
$submit-data
440+
</Menu.Item>
441+
<Menu.Item
442+
onClick={() => submitData(true)}
443+
icon={<IconSquareCheck color={theme.colors.yellow[6]} stroke={1.5} />}
444+
>
445+
POST data
446+
</Menu.Item>
447+
</Menu.Dropdown>
448+
</Menu>
379449
<Tooltip
380450
label="Disabled until data submission has succeeeded"
381451
openDelay={1000}

0 commit comments

Comments
 (0)