Skip to content

Commit

Permalink
feat: allow arbitrary response for suggester (#900)
Browse files Browse the repository at this point in the history
* feat: allow arbitrary response for suggester

* fix: do not allow arbitrary in table

* feat: handle arbitrary variable for suggester

* chore: rename "allowArbitrary" -> "allowArbitraryResponse"

* chore: review changes
  • Loading branch information
QRuhier authored Feb 18, 2025
1 parent f51cbfb commit f212182
Show file tree
Hide file tree
Showing 16 changed files with 461 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/constants/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@ const dictionary: Dictionary = {
fr: 'Retrouver dans le questionnaire',
en: 'Retrieve in the questionnaire',
},
allowArbitraryResponse: {
fr: 'Autoriser une réponse libre',
en: 'Allow an arbitrary response',
},
list: {
fr: 'Liste',
en: 'List',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function formToState(form) {
y,
z,
mesureLevel,
arbitraryVariableOfVariableId,
codeListReference,
codeListReferenceLabel,
isCollected,
Expand All @@ -61,6 +62,7 @@ export function formToState(form) {
x,
y,
z,
arbitraryVariableOfVariableId,
isCollected,
alternativeLabel,
mesureLevel,
Expand Down Expand Up @@ -94,6 +96,7 @@ export function storeToForm(currentStore) {
x,
y,
z,
arbitraryVariableOfVariableId,
isCollected = '1',
alternativeLabel,
mesureLevel,
Expand All @@ -107,6 +110,7 @@ export function storeToForm(currentStore) {
x,
y,
z,
arbitraryVariableOfVariableId,
isCollected,
alternativeLabel,
mesureLevel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
const { RADIO } = DATATYPE_VIS_HINT;

export const defaultState = {
allowArbitraryResponse: false,
mandatory: false,
visHint: RADIO,
// [DEFAULT_CODES_LIST_SELECTOR_PATH]: cloneDeep(CodesListDefaultState),
};

export const defaultForm = {
allowArbitraryResponse: false,
mandatory: false,
visHint: RADIO,
// [DEFAULT_CODES_LIST_SELECTOR_PATH]: cloneDeep(CodesListDefaultForm),
Expand All @@ -24,13 +26,15 @@ export const defaultForm = {
export function formToState(form, transformers) {
const {
id,
allowArbitraryResponse,
mandatory,
visHint,
[DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListForm,
} = form;

return {
id,
allowArbitraryResponse,
mandatory,
visHint,
[DEFAULT_CODES_LIST_SELECTOR_PATH]:
Expand All @@ -39,10 +43,11 @@ export function formToState(form, transformers) {
}

export function stateToForm(currentState, transformers) {
const { id, visHint, mandatory } = currentState;
const { id, allowArbitraryResponse, visHint, mandatory } = currentState;

return {
id,
allowArbitraryResponse,
mandatory,
visHint,
[DEFAULT_CODES_LIST_SELECTOR_PATH]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const defaultMeasureForm = {
type: SIMPLE,
[SIMPLE]: defaultMeasureSimpleState,
[SINGLE_CHOICE]: {
allowArbitraryResponse: false,
[DEFAULT_CODES_LIST_SELECTOR_PATH]: cloneDeep(CodesListDefaultForm),
visHint: RADIO,
},
Expand Down
7 changes: 7 additions & 0 deletions src/model/remote-to-stores.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export function questionnaireRemoteToStores(remote, currentStores = {}) {
remote.Child,
collectedVariables,
);

const arbitraryVariables = Component.getArbitraryVariablesFromRemote(
remote.Child,
collectedVariables,
);

const codesListsStore = CodesList.remoteToStore(
codesLists,
variableclarification,
Expand All @@ -56,6 +62,7 @@ export function questionnaireRemoteToStores(remote, currentStores = {}) {
responsesByVariable,
codesListsStore,
variableclarification,
arbitraryVariables,
),
};
// Components store
Expand Down
21 changes: 20 additions & 1 deletion src/model/transformations/collected-variable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function remoteToStore(
responsesByVariable,
codesListsStore,
variableclarification,
arbitraryVariables,
) {
remote.forEach((variable) => {
if (variableclarification) {
Expand All @@ -45,9 +46,26 @@ export function remoteToStore(
}
}
}
if (arbitraryVariables) {
const extendedArbitraryVariable = arbitraryVariables.find(
(arbitraryVariable) =>
arbitraryVariable.CollectedVariableReference === variable.id,
);
if (extendedArbitraryVariable) {
variable.arbitraryVariableOfVariableId =
extendedArbitraryVariable.arbitraryVariableOfVariableId;
}
}
});
return remote.reduce((acc, ev) => {
const { Name: name, Label: label, CodeListReference, z, mesureLevel } = ev;
const {
Name: name,
Label: label,
CodeListReference,
z,
mesureLevel,
arbitraryVariableOfVariableId,
} = ev;
const id = ev.id || uuid();

const formatSingleRemote = remoteToStateFormatSimple({
Expand All @@ -69,6 +87,7 @@ export function remoteToStore(
...responsesByVariable[id],
z,
mesureLevel,
arbitraryVariableOfVariableId,
},
};
}, {});
Expand Down
80 changes: 80 additions & 0 deletions src/model/transformations/collected-variable.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,86 @@ describe('collected variable tranformations', () => {
output,
);
});

test('should add the arbitrary variables to the collected', () => {
const input = [
{
id: 'm6aty8by',
Name: 'SUGGESTER',
type: 'CollectedVariableType',
Label: 'SUGGESTER label',
Datatype: {
type: 'TextDatatypeType',
Pattern: '',
typeName: 'TEXT',
MaxLength: 1,
},
CodeListReference: 'id',
},
{
id: 'm6atzjnb',
Name: 'SUGGESTER_ARBITRARY',
type: 'CollectedVariableType',
Label: 'SUGGESTER_ARBITRARY label',
Datatype: {
type: 'TextDatatypeType',
typeName: 'TEXT',
MaxLength: 249,
},
arbitraryVariableOfVariableId: 'm6aty8by',
},
];
const arbitraryVariables = [
{
Datatype: {
type: 'TextDatatypeType',
typeName: 'TEXT',
MaxLength: 249,
},
mandatory: false,
CollectedVariableReference: 'm6atzjnb',
arbitraryVariableOfVariableId: 'm6aty8by',
},
];
const responsesByVariable = { m6aty8by: {} };
const codesListStore = { id: { label: 'label' } };
const variableclarification = [];
const output = {
m6aty8by: {
id: 'm6aty8by',
name: 'SUGGESTER',
label: 'SUGGESTER label',
type: TEXT,
codeListReference: 'id',
codeListReferenceLabel: 'label',
TEXT: {
maxLength: 1,
pattern: '',
},
},
m6atzjnb: {
id: 'm6atzjnb',
name: 'SUGGESTER_ARBITRARY',
label: 'SUGGESTER_ARBITRARY label',
type: TEXT,
codeListReferenceLabel: '',
TEXT: {
maxLength: 249,
},
arbitraryVariableOfVariableId: 'm6aty8by',
},
};

expect(
remoteToStore(
input,
responsesByVariable,
codesListStore,
variableclarification,
arbitraryVariables,
),
).toEqual(output);
});
});
describe('remoteToComponentState', () => {
test('should return the state representation of a collected variable', () => {
Expand Down
98 changes: 98 additions & 0 deletions src/model/transformations/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,61 @@ export const getClarificarionfromremote = (Children, collectedVariables) => {
return variableClarification;
};

/*
* Get the list of questions containing an attribute "ArbitraryResponse".
* Currently we only look at single response questions.
*/
const getArbitraryQuestions = (Children) => {
const arbitraryQuestions = [];
const childr = Children.filter((children) => children.Child?.length !== 0);
childr.forEach((item) => {
item.Child?.forEach((component) => {
// component is a subsequence
if (component.type === 'SequenceType') {
component.Child.forEach((question) => {
if (
// single choice question
question.questionType === SINGLE_CHOICE &&
question.ArbitraryResponse !== undefined
) {
arbitraryQuestions.push(question);
}
});
} else if (
// component is a single choice question
component.questionType === SINGLE_CHOICE &&
component.ArbitraryResponse !== undefined
) {
arbitraryQuestions.push(component);
}
});
});
return arbitraryQuestions;
};

/*
* Get the list of arbitrary variables.
* An arbitrary variable is the "ArbitraryResponse" object of a question,
* extented with "arbitraryVariableOfVariableId" which is the corresponding Response variable id.
*/
export const getArbitraryVariablesFromRemote = (Children) => {
const arbitraryVariables = [];
const arbitraryQuestions = getArbitraryQuestions(Children);

arbitraryQuestions.forEach((question) => {
// a question with arbitrary is a singleReponse question so it has only one Response
const responseVariableId = question.Response[0].CollectedVariableReference;

const arbitraryVariable = {
...question.ArbitraryResponse,
arbitraryVariableOfVariableId: responseVariableId,
};
arbitraryVariables.push(arbitraryVariable);
});

return arbitraryVariables;
};

function remoteToVariableResponseNested(children = [], acc = {}) {
children.forEach((child) => {
const {
Expand Down Expand Up @@ -226,6 +281,7 @@ function remoteToState(remote, componentGroup, codesListsStore) {
Control: controls,
Response: responses,
ClarificationQuestion: responsesClarification,
ArbitraryResponse: arbitraryResponse,
ResponseStructure: responseStructure,
Child: children,
parent,
Expand All @@ -250,6 +306,10 @@ function remoteToState(remote, componentGroup, codesListsStore) {
responseFinal = responseFinal.concat(clar.Response);
});
}
if (arbitraryResponse !== undefined) {
responseFinal.push(arbitraryResponse);
}

const state = {
id,
name,
Expand Down Expand Up @@ -629,6 +689,37 @@ function getClarificationResponseTableQuestion(
};
}

function getArbitraryResponse(collectedVariablesStore, collectedVariables) {
const collectedVariableQuestions = [];
Object.values(collectedVariablesStore).forEach((collec) => {
if (collectedVariables !== undefined) {
collectedVariables.forEach((variables) => {
if (collec.id === variables) {
collectedVariableQuestions.push(collec);
}
});
}
});

for (const collected of collectedVariableQuestions) {
const isArbitraryVariable =
collected.arbitraryVariableOfVariableId !== undefined;
// we can have only one collected variable that is an arbitrary variable
if (isArbitraryVariable) {
const responseModel = {
mandatory: false,
typeName: collected.type,
maxLength: collected.TEXT.maxLength,
collectedVariable: collected.id,
};
const arbitraryResponse = Response.stateToRemote(responseModel);
return arbitraryResponse;
}
}

return undefined;
}

function storeToRemoteNested(
state,
store,
Expand Down Expand Up @@ -686,8 +777,15 @@ function storeToRemoteNested(
responsesClarification,
flowControl,
);

remote.FlowControl = remoteclarification.flowcontrolefinal;
remote.ClarificationQuestion = remoteclarification.ClarificationQuestion;

const remoteArbitrary = getArbitraryResponse(
collectedVariablesStore,
collectedVariables,
);
remote.ArbitraryResponse = remoteArbitrary;
}
if (
responseFormat.type === MULTIPLE_CHOICE &&
Expand Down
Loading

0 comments on commit f212182

Please sign in to comment.