Skip to content

Commit

Permalink
Fix remove empty answers, save and form preview for facade compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
fongsean committed Dec 18, 2023
1 parent 023337f commit 3b9e044
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 50 deletions.
2 changes: 1 addition & 1 deletion apps/smart-forms-app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ VITE_ONTOSERVER_URL=https://r4.ontoserver.csiro.au/fhir
VITE_FORMS_SERVER_URL=https://smartforms.csiro.au/api/fhir

VITE_LAUNCH_SCOPE=fhirUser online_access openid profile patient/Condition.rs patient/Observation.rs launch patient/Encounter.rs patient/QuestionnaireResponse.cruds patient/Patient.rs
VITE_LAUNCH_CLIENT_ID=a57d90e3-5f69-4b92-aa2e-2992180863c0
VITE_LAUNCH_CLIENT_ID=a57d90e3-5f69-4b92-aa2e-2992180863c1

VITE_IN_APP_POPULATE=true

Expand Down
2 changes: 1 addition & 1 deletion apps/smart-forms-app/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ VITE_ONTOSERVER_URL=https://r4.ontoserver.csiro.au/fhir
VITE_FORMS_SERVER_URL=https://smartforms.csiro.au/api/fhir

VITE_LAUNCH_SCOPE=fhirUser online_access openid profile patient/Condition.rs patient/Observation.rs launch patient/Encounter.rs patient/QuestionnaireResponse.cruds patient/Patient.rs
VITE_LAUNCH_CLIENT_ID=a57d90e3-5f69-4b92-aa2e-2992180863c0
VITE_LAUNCH_CLIENT_ID=a57d90e3-5f69-4b92-aa2e-2992180863c1

VITE_IN_APP_POPULATE=true

Expand Down
4 changes: 2 additions & 2 deletions apps/smart-forms-app/src/api/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const HEADERS = {
'Cache-Control': 'no-cache',
'Content-Type': 'application/json+fhir;charset=utf-8',
Accept: 'application/json+fhir;charset=utf-8'
'Content-Type': 'application/json;charset=utf-8',
Accept: 'application/json;charset=utf-8'
};
11 changes: 7 additions & 4 deletions apps/smart-forms-app/src/api/saveQr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { qrToHTML } from '../features/preview/utils/preview.ts';
import { fetchQuestionnaireById } from './client.ts';
import cloneDeep from 'lodash.clonedeep';
import { HEADERS } from './headers.ts';
import { removeHiddenAnswersFromResponse } from '@aehrc/smart-forms-renderer';
import { removeEmptyAnswersFromResponse } from '@aehrc/smart-forms-renderer';

/**
* POST questionnaire to SMART Health IT when opening it to ensure response-saving can be performed
Expand Down Expand Up @@ -55,7 +55,7 @@ export async function saveProgress(
questionnaireResponse: QuestionnaireResponse,
saveStatus: 'in-progress' | 'completed'
) {
const responseToSave = removeHiddenAnswersFromResponse(
const responseToSave = removeEmptyAnswersFromResponse(
questionnaire,
cloneDeep(questionnaireResponse)
);
Expand Down Expand Up @@ -87,7 +87,7 @@ export async function saveQuestionnaireResponse(
user: Practitioner,
questionnaire: Questionnaire,
questionnaireResponse: QuestionnaireResponse
): Promise<QuestionnaireResponse> {
) {
let requestUrl = 'QuestionnaireResponse';
let method = 'POST';
let questionnaireResponseToSave: QuestionnaireResponse = cloneDeep(questionnaireResponse);
Expand All @@ -111,6 +111,7 @@ export async function saveQuestionnaireResponse(
authored: dayjs().format()
};

// TODO pre-pop should filter out all empty strings really
// Add additional attributes depending on whether questionnaire has been saved before
if (questionnaireResponseToSave.id) {
requestUrl += '/' + questionnaireResponseToSave.id;
Expand All @@ -124,11 +125,13 @@ export async function saveQuestionnaireResponse(
);
}

const modifiedHeaders = { ...HEADERS, prefer: 'return=representation' };

return client.request({
url: requestUrl,
method: method,
body: JSON.stringify(questionnaireResponseToSave),
headers: HEADERS
headers: modifiedHeaders
});
}

Expand Down
24 changes: 10 additions & 14 deletions apps/smart-forms-app/src/features/preview/utils/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@ export function qrToHTML(
): string {
if (!questionnaireResponse.item || questionnaireResponse.item.length === 0) return '';

let QrHtml = `<div style="font-size:20px; font-weight: bold" data-test="response-questionnaire-title">${questionnaire.title}</div><hr />`;
let QrHtml = `<div style="font-size:20px; font-weight: bold">${questionnaire.title}</div><hr/>`;

for (const topLevelQRItem of questionnaireResponse.item) {
const topLevelQRItemHTML = qrItemToHTML(topLevelQRItem);
if (topLevelQRItemHTML) {
QrHtml += topLevelQRItemHTML;
}
}

return `<div>${QrHtml}</div>`;
return `<div xmlns="http://www.w3.org/1999/xhtml">${QrHtml}</div>`;
}

export function qrItemToHTML(topLevelQRItem: QuestionnaireResponseItem) {
Expand Down Expand Up @@ -125,29 +124,26 @@ function renderItemDiv(item: QuestionnaireResponseItem, nestedLevel: number) {
item.answer.forEach((answer) => {
const answerValueInString = he.encode(qrItemAnswerValueTypeSwitcher(answer));
if (answerValueInString === '') {
qrItemAnswer +=
'<div style="color: red;" data-test="response-item-answer">Undefined answer</div>';
qrItemAnswer += '<div style="color: red">Undefined answer</div>';
} else {
qrItemAnswer += `<div data-test="response-item-answer">${
qrItemAnswer += `<div>${
answerValueInString[0].toUpperCase() + answerValueInString.slice(1)
}</div>`;
}
});

const qrItemRender = `<div style="flex:40%;" data-test="response-item-text">${item.text}</div>
<div style="flex: 10%;"></div>
<div style="flex: 50%;">${qrItemAnswer}</div>`;
const qrItemRender = `<div style="flex:40%">${item.text}</div><div style="flex: 10%"></div><div style="flex: 50%">${qrItemAnswer}</div>`;

return `<div style="margin-top: ${
nestedLevel === 0 ? '20px' : '10px'
}; display: flex; flex-wrap: wrap;">${qrItemRender}</div>`;
}; display: flex; flex-wrap: wrap">${qrItemRender}</div>`;
}

function renderGroupHeadingDiv(item: QuestionnaireResponseItem, nestedLevel: number) {
const fontSize = nestedLevel === 0 ? '18px' : '15px';
const headingText = he.encode(item.text ?? '');

return `<div style="font-size: ${fontSize}; font-weight: bold; margin-top: 15px" data-test="response-group-heading">${headingText}</div>`;
return `<div style="font-size: ${fontSize}; font-weight: bold; margin-top: 15px">${headingText}</div>`;
}

function renderRepeatGroupItemHeadingDiv() {
Expand Down Expand Up @@ -202,7 +198,7 @@ function qrItemAnswerValueTypeSwitcher(answer: QuestionnaireResponseItemAnswer):

function renderGroupBottomMargin(repeatGroupItemStatus: RepeatGroupItemStatus) {
if (repeatGroupItemStatus === 'first' || repeatGroupItemStatus === 'middle') {
return `<div style="height: 1px; width: 100%; background-color: #E5EAF2;margin-top: 15px;"></div>`;
return `<div style="height: 1px; width: 100%; background-color: #E5EAF2;margin-top: 15px"></div>`;
} else {
return `<div style="margin-bottom: 30px;"></div>`;
}
Expand All @@ -212,8 +208,8 @@ function renderGeneralBottomMargin(
nestedLevel: number,
nextItem: QuestionnaireResponseItem | undefined
) {
const smallMarginDiv = `<div style="margin: 20px 0 20px;"></div>`;
const largeMarginDiv = `<div style="margin: 55px 0 20px;"></div>`;
const smallMarginDiv = `<div style="margin: 20px 0 20px"></div>`;
const largeMarginDiv = `<div style="margin: 55px 0 20px"></div>`;

if (nestedLevel !== 0) {
return '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { qrToHTML } from '../../../preview/utils/preview.ts';
import { Helmet } from 'react-helmet';
import PageHeading from '../../../dashboard/components/DashboardPages/PageHeading.tsx';
import {
removeHiddenAnswersFromResponse,
removeEmptyAnswersFromResponse,
useQuestionnaireResponseStore,
useQuestionnaireStore
} from '@aehrc/smart-forms-renderer';
Expand All @@ -39,7 +39,7 @@ function FormPreview() {
return <FormInvalid />;
}

const cleanResponse = removeHiddenAnswersFromResponse(sourceQuestionnaire, updatableResponse);
const cleanResponse = removeEmptyAnswersFromResponse(sourceQuestionnaire, updatableResponse);
const parsedHTML = parse(qrToHTML(sourceQuestionnaire, cleanResponse));

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,20 @@ function SaveProgressAction(props: SaveProgressSpeedDialActionProps) {
'in-progress'
);

if (!savedResponse) {
// If the response is null or undefined, then the save has failed
if (savedResponse === null || savedResponse === undefined) {
enqueueSnackbar(saveErrorMessage, {
variant: 'error'
});
return;
}

setUpdatableResponseAsSaved(savedResponse);
// Use saved validated response as the new updatable response
if (savedResponse && savedResponse.resourceType === 'QuestionnaireResponse') {
setUpdatableResponseAsSaved(savedResponse);
} else {
setUpdatableResponseAsSaved(updatableResponse);
}

// Refetch existing responses if prop is provided
if (refetchResponses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { saveQuestionnaireResponse } from '../../../../api/saveQr.ts';
import cloneDeep from 'lodash.clonedeep';
import { LoadingButton } from '@mui/lab';
import {
removeHiddenAnswersFromResponse,
removeEmptyAnswersFromResponse,
useQuestionnaireResponseStore,
useQuestionnaireStore
} from '@aehrc/smart-forms-renderer';
Expand Down Expand Up @@ -79,7 +79,7 @@ function BlockerUnsavedFormDialog(props: Props) {
setIsSaving(true);

let responseToSave = cloneDeep(updatableResponse);
responseToSave = removeHiddenAnswersFromResponse(sourceQuestionnaire, responseToSave);
responseToSave = removeEmptyAnswersFromResponse(sourceQuestionnaire, responseToSave);

setIsSaving(true);
responseToSave.status = 'in-progress';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { useEffect } from 'react';
import { Dialog, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
import {
removeHiddenAnswersFromResponse,
removeEmptyAnswersFromResponse,
useQuestionnaireResponseStore,
useQuestionnaireStore
} from '@aehrc/smart-forms-renderer';
Expand Down Expand Up @@ -54,7 +54,7 @@ function AutoSaveDialog(props: AutoSaveDialogProps) {
return;
}

const responseToSave = removeHiddenAnswersFromResponse(
const responseToSave = removeEmptyAnswersFromResponse(
sourceQuestionnaire,
cloneDeep(updatableResponse)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
} from '@mui/material';
import { LoadingButton } from '@mui/lab';
import {
removeHiddenAnswersFromResponse,
removeEmptyAnswersFromResponse,
useQuestionnaireResponseStore,
useQuestionnaireStore
} from '@aehrc/smart-forms-renderer';
Expand Down Expand Up @@ -75,7 +75,7 @@ function TokenTimerDialog(props: TokenTimerDialogProps) {
}

setIsSaving(true);
const responseToSave = removeHiddenAnswersFromResponse(
const responseToSave = removeEmptyAnswersFromResponse(
sourceQuestionnaire,
cloneDeep(updatableResponse)
);
Expand Down
4 changes: 2 additions & 2 deletions apps/smart-forms-app/src/features/viewer/ResponsePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { qrToHTML } from '../preview/utils/preview.ts';
import { Helmet } from 'react-helmet';
import PageHeading from '../dashboard/components/DashboardPages/PageHeading.tsx';
import {
removeHiddenAnswersFromResponse,
removeEmptyAnswersFromResponse,
useQuestionnaireResponseStore,
useQuestionnaireStore
} from '@aehrc/smart-forms-renderer';
Expand All @@ -49,7 +49,7 @@ function ResponsePreview() {
return <ViewerInvalid questionnaire={sourceQuestionnaire} />;
}

const responseCleaned = removeHiddenAnswersFromResponse(sourceQuestionnaire, sourceResponse);
const responseCleaned = removeEmptyAnswersFromResponse(sourceQuestionnaire, sourceResponse);
const parsedHTML = parse(qrToHTML(sourceQuestionnaire, responseCleaned));

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
import type { EnableWhenExpression, EnableWhenItems } from '../interfaces/enableWhen.interface';
import { isHidden } from './qItem';

interface removeHiddenAnswersParams {
interface removeEmptyAnswersParams {
questionnaire: Questionnaire;
questionnaireResponse: QuestionnaireResponse;
enableWhenIsActivated: boolean;
Expand All @@ -33,11 +33,11 @@ interface removeHiddenAnswersParams {
}

/**
* Recursively go through the questionnaireResponse and remove qrItems whose qItems are hidden in the form
* Recursively go through the questionnaireResponse and remove qrItems whose qItems are empty in the form
*
* @author Sean Fong
*/
export function removeHiddenAnswers(params: removeHiddenAnswersParams): QuestionnaireResponse {
export function removeEmptyAnswers(params: removeEmptyAnswersParams): QuestionnaireResponse {
const {
questionnaire,
questionnaireResponse,
Expand All @@ -59,7 +59,7 @@ export function removeHiddenAnswers(params: removeHiddenAnswersParams): Question

topLevelQRItems.forEach((qrItem, i) => {
const qItem = topLevelQItems[i];
const newTopLevelQRItem = readQuestionnaireResponseItemRecursive({
const newTopLevelQRItem = removeEmptyAnswersFromItemRecursive({
qItem,
qrItem,
enableWhenIsActivated,
Expand All @@ -74,16 +74,16 @@ export function removeHiddenAnswers(params: removeHiddenAnswersParams): Question
return questionnaireResponse;
}

interface readQuestionnaireResponseItemRecursiveParams {
interface removeEmptyAnswersFromItemRecursiveParams {
qItem: QuestionnaireItem;
qrItem: QuestionnaireResponseItem;
enableWhenIsActivated: boolean;
enableWhenItems: EnableWhenItems;
enableWhenExpressions: Record<string, EnableWhenExpression>;
}

function readQuestionnaireResponseItemRecursive(
params: readQuestionnaireResponseItemRecursiveParams
function removeEmptyAnswersFromItemRecursive(
params: removeEmptyAnswersFromItemRecursiveParams
): QuestionnaireResponseItem | null {
const { qItem, qrItem, enableWhenIsActivated, enableWhenItems, enableWhenExpressions } = params;

Expand Down Expand Up @@ -116,7 +116,7 @@ function readQuestionnaireResponseItemRecursive(
) {
// Save qrItem if linkIds of current qItem and qrItem are the same
if (qrItems[qrItemIndex] && qItems[qItemIndex].linkId === qrItems[qrItemIndex].linkId) {
const newQrItem = readQuestionnaireResponseItemRecursive({
const newQrItem = removeEmptyAnswersFromItemRecursive({
qItem: qItems[qItemIndex],
qrItem: qrItems[qrItemIndex],
enableWhenIsActivated,
Expand Down Expand Up @@ -144,17 +144,49 @@ function readQuestionnaireResponseItemRecursive(
return { ...qrItem, item: newQrItems };
}

// Also perform checking if answer exists
return qrItem['answer'] ? qrItem : null;
// Also perform checks if answer exists
return answerIsEmpty(
qItem,
qrItem,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
)
? null
: qrItem;
}

// Process non-group items
return isHidden({
questionnaireItem: qItem,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
})
return answerIsEmpty(qItem, qrItem, enableWhenIsActivated, enableWhenItems, enableWhenExpressions)
? null
: { ...qrItem };
}

function answerIsEmpty(
qItem: QuestionnaireItem,
qrItem: QuestionnaireResponseItem,
enableWhenIsActivated: boolean,
enableWhenItems: EnableWhenItems,
enableWhenExpressions: Record<string, EnableWhenExpression>
) {
if (
isHidden({
questionnaireItem: qItem,
enableWhenIsActivated,
enableWhenItems,
enableWhenExpressions
})
) {
return true;
}

if (!qrItem.answer) {
return true;
}

if (qrItem.answer[0]?.valueString === '') {
return true;
}

return false;
}

0 comments on commit 3b9e044

Please sign in to comment.