Skip to content

Commit

Permalink
#1137 - support for add and remove question group. unit test for dat…
Browse files Browse the repository at this point in the history
…a flow of QG observations. basic change rule service to support RQG like avni client.
  • Loading branch information
petmongrels committed Apr 5, 2024
1 parent 98eb4d1 commit c475e2d
Show file tree
Hide file tree
Showing 23 changed files with 493 additions and 87 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"material-table": "1.43.0",
"moment": "^2.22.2",
"openchs-idi": "git+https://github.com/avniproject/openchs-idi#b6c57e051b91ed4bc2634f4f087dba51cc3a01c8",
"openchs-models": "1.31.58",
"openchs-models": "1.31.63",
"popper.js": "^1.14.3",
"prismjs": "^1.17.1",
"prop-types": "^15.7.2",
Expand Down
2 changes: 1 addition & 1 deletion src/common/subjectModelMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const mapConcept = conceptJson => {
};

function looksLikeRepeatableQuestionGroupValue(value) {
return _.isArrayLike(value) && value.length > 1 && _.isArrayLike(value[0]);
return _.isArrayLike(value) && value.length > 0 && _.isArrayLike(value[0]);
}

export function mapObservation(observationJson) {
Expand Down
8 changes: 6 additions & 2 deletions src/dataEntryApp/components/FormElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export const FormElement = ({
filteredFormElements,
ignoreLineBreak,
isGrid,
updateObs
updateObs,
addNewQuestionGroup,
removeQuestionGroup
}) => {
const type = formElement.getType();
if (type === Concept.dataType.Id) {
Expand All @@ -73,7 +75,9 @@ export const FormElement = ({
uuid,
filteredFormElements,
isGrid,
updateObs
updateObs,
addNewQuestionGroup,
removeQuestionGroup
};
const Element = elements[type];
return (
Expand Down
6 changes: 5 additions & 1 deletion src/dataEntryApp/components/FormElementGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const FormElementGroup = ({
validationResults,
filteredFormElements,
entity,
renderChildren
renderChildren,
addNewQuestionGroup,
removeQuestionGroup
}) => {
const nestedElements = nestedFormElements(filteredFormElements);
return (
Expand Down Expand Up @@ -47,6 +49,8 @@ export const FormElementGroup = ({
feIndex={index}
filteredFormElements={filteredFormElements}
updateObs={updateObs}
addNewQuestionGroup={addNewQuestionGroup}
removeQuestionGroup={removeQuestionGroup}
>
{fe}
</FormElement>
Expand Down
12 changes: 9 additions & 3 deletions src/dataEntryApp/components/QuestionGroupFormElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export default function QuestionGroupFormElement({
questionGroupIndex
}) {
const allChildren = sortBy(
filter(filteredFormElements, ffe => get(ffe, "group.uuid") === formElement.uuid && !ffe.voided),
filter(
filteredFormElements,
ffe =>
get(ffe, "group.uuid") === formElement.uuid &&
!ffe.voided &&
(_.isNil(questionGroupIndex) || ffe.questionGroupIndex === questionGroupIndex)
),
"displayOrder"
);
const textNumericAndNotes = filter(allChildren, ({ concept }) =>
Expand Down Expand Up @@ -56,7 +62,7 @@ export default function QuestionGroupFormElement({
validationResults={validationResults}
uuid={childFormElement.uuid}
update={value => {
updateObs(formElement, value, childFormElement);
updateObs(formElement, value, childFormElement, questionGroupIndex);
}}
feIndex={childFormElement.displayOrder}
filteredFormElements={filteredFormElements}
Expand All @@ -77,7 +83,7 @@ export default function QuestionGroupFormElement({
validationResults={validationResults}
uuid={childFormElement.uuid}
update={value => {
updateObs(formElement, value, childFormElement);
updateObs(formElement, value, childFormElement, questionGroupIndex);
}}
feIndex={childFormElement.displayOrder}
filteredFormElements={filteredFormElements}
Expand Down
67 changes: 54 additions & 13 deletions src/dataEntryApp/components/RepeatableQuestionGroupElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,69 @@ import React from "react";
import { RepeatableQuestionGroup } from "openchs-models";
import QuestionGroupFormElement from "./QuestionGroupFormElement";
import _ from "lodash";
import Button from "@material-ui/core/Button";
import { LineBreak } from "../../common/components/utils";

function AddMoreButton({ addNewQuestionGroup, formElement }) {
return (
<Button
onClick={() => addNewQuestionGroup(formElement.concept)}
color="primary"
>{`Add One More - ${formElement.concept.name}`}</Button>
);
}

function RemoveButton({ removeQuestionGroup, formElement, index }) {
return (
<Button onClick={() => removeQuestionGroup(formElement.concept, index)} color="primary">
{"Remove"}
</Button>
);
}

export function RepeatableQuestionGroupElement({
formElement,
obsHolder,
validationResults,
filteredFormElements,
updateObs
updateObs,
addNewQuestionGroup,
removeQuestionGroup
}) {
let repeatableQuestionGroup = obsHolder.findObservation(formElement.concept);
if (_.isNil(repeatableQuestionGroup)) repeatableQuestionGroup = new RepeatableQuestionGroup();
return repeatableQuestionGroup.getValue().map((x, index) => {
const hasNoObservation = _.isNil(repeatableQuestionGroup);
if (hasNoObservation) repeatableQuestionGroup = new RepeatableQuestionGroup();
const repeatableQuestionGroupValue = repeatableQuestionGroup.getValue();
const hasMultipleElements = repeatableQuestionGroupValue.length > 1;
return repeatableQuestionGroupValue.map((x, index) => {
const isLastElement = !hasNoObservation && repeatableQuestionGroupValue.length === index + 1;
return (
<QuestionGroupFormElement
formElement={formElement}
filteredFormElements={filteredFormElements}
obsHolder={obsHolder}
updateObs={updateObs}
validationResults={validationResults}
isRepeatable={true}
questionGroupIndex={index}
key={index}
/>
<>
<QuestionGroupFormElement
formElement={formElement}
filteredFormElements={filteredFormElements}
obsHolder={obsHolder}
updateObs={updateObs}
validationResults={validationResults}
isRepeatable={true}
questionGroupIndex={index}
key={index}
/>
{(hasMultipleElements || isLastElement) && <LineBreak num={1} />}
<>
{hasMultipleElements && (
<RemoveButton
formElement={formElement}
index={index}
removeQuestionGroup={removeQuestionGroup}
/>
)}
{isLastElement && (
<AddMoreButton formElement={formElement} addNewQuestionGroup={addNewQuestionGroup} />
)}
</>
{!isLastElement && <LineBreak num={2} />}
</>
);
});
}
61 changes: 50 additions & 11 deletions src/dataEntryApp/reducers/commonFormUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,20 +304,14 @@ const handleValidationResult = (newValidationResults, existingValidationResults)
return existingValidationResultClones;
};

const updateObservations = (
formElement,
value,
function postObservationsUpdate(
entity,
formElement,
observationsHolder,
obsValue,
existingValidationResults,
childFormElement
) => {
const obsValue = formElementService.updateObservations(
observationsHolder,
formElement,
value,
childFormElement
);
) {
const formElementStatuses = getFormElementStatuses(
entity,
formElement.formElementGroup,
Expand All @@ -337,8 +331,51 @@ const updateObservations = (
childFormElement
);
return { filteredFormElements, validationResults };
}

const updateObservations = (
formElement,
value,
entity,
observationsHolder,
existingValidationResults,
childFormElement,
questionGroupIndex
) => {
const obsValue = formElementService.updateObservations(
observationsHolder,
formElement,
value,
childFormElement,
questionGroupIndex
);
const { filteredFormElements, validationResults } = postObservationsUpdate(
entity,
formElement,
observationsHolder,
obsValue,
existingValidationResults,
childFormElement
);
return { filteredFormElements, validationResults };
};

function getRepeatableQuestionGroup(observations, concept) {
const observationsHolder = new ObservationsHolder(observations);
const observation = observationsHolder.findObservation(concept);
return observation.getValueWrapper();
}

function addNewQuestionGroup(observations, concept) {
const repeatableQuestionGroup = getRepeatableQuestionGroup(observations, concept);
repeatableQuestionGroup.addQuestionGroup();
}

function removeQuestionGroup(observations, concept, index) {
const repeatableQuestionGroup = getRepeatableQuestionGroup(observations, concept);
repeatableQuestionGroup.removeQuestionGroup(index);
}

const getValidationResult = (validationResults, formElementIdentifier) =>
find(
validationResults,
Expand All @@ -351,5 +388,7 @@ export default {
onPrevious,
handleValidationResult,
updateObservations,
getValidationResult
getValidationResult,
addNewQuestionGroup,
removeQuestionGroup
};
127 changes: 127 additions & 0 deletions src/dataEntryApp/reducers/commonFormUtil.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import commonFormUtil from "./commonFormUtil";
import { Concept, ObservationsHolder } from "openchs-models";
import EntityFactory from "../test/EntityFactory";
import { assert } from "chai";
import TestKeyValueFactory from "../test/TestKeyValueFactory";

describe("question group and repeatable question groups", () => {
let qgFormElement;
let formElement;

beforeEach(() => {
const form = EntityFactory.createForm2({ uuid: "form-1" });
const feg = EntityFactory.createFormElementGroup2({ uuid: "feg-1", form: form });
const qgConcept = EntityFactory.createConcept2({
uuid: "qg-c-1",
dataType: Concept.dataType.QuestionGroup
});
const textConcept = EntityFactory.createConcept2({
uuid: "c-1",
name: "c-1",
dataType: Concept.dataType.Text
});

qgFormElement = EntityFactory.createFormElement2({
uuid: "qg-fe-1",
formElementGroup: feg,
concept: qgConcept
});
formElement = EntityFactory.createFormElement2({
uuid: "fe-1",
formElementGroup: feg,
concept: textConcept,
group: qgFormElement
});
});

it("should create and update question group obs", function() {
const subject = EntityFactory.createSubject({});
const observationsHolder = new ObservationsHolder(subject.observations);
commonFormUtil.updateObservations(
qgFormElement,
"a",
subject,
observationsHolder,
[],
formElement,
null
);
assert.equal(
"a",
observationsHolder.findQuestionGroupObservation(formElement.concept, qgFormElement).getValue()
);
commonFormUtil.updateObservations(
qgFormElement,
"b",
subject,
observationsHolder,
[],
formElement,
null
);
assert.equal(
"b",
observationsHolder.findQuestionGroupObservation(formElement.concept, qgFormElement).getValue()
);
});

it("should create and update repeatable question group obs", function() {
const keyValue = TestKeyValueFactory.create({ key: "repeatable", value: true });
qgFormElement.keyValues = [keyValue];
const subject = EntityFactory.createSubject({});
const observationsHolder = new ObservationsHolder(subject.observations);
commonFormUtil.updateObservations(
qgFormElement,
"a",
subject,
observationsHolder,
[],
formElement,
0
);
assert.equal(
"a",
observationsHolder
.findQuestionGroupObservation(formElement.concept, qgFormElement, 0)
.getValue()
);
commonFormUtil.updateObservations(
qgFormElement,
"b",
subject,
observationsHolder,
[],
formElement,
0
);
assert.equal(
"b",
observationsHolder
.findQuestionGroupObservation(formElement.concept, qgFormElement, 0)
.getValue()
);

commonFormUtil.addNewQuestionGroup(observationsHolder.observations, qgFormElement.concept);
commonFormUtil.updateObservations(
qgFormElement,
"c",
subject,
observationsHolder,
[],
formElement,
1
);
assert.equal(
"b",
observationsHolder
.findQuestionGroupObservation(formElement.concept, qgFormElement, 0)
.getValue()
);
assert.equal(
"c",
observationsHolder
.findQuestionGroupObservation(formElement.concept, qgFormElement, 1)
.getValue()
);
});
});
Loading

0 comments on commit c475e2d

Please sign in to comment.