From bc7067ea80489738d21ffaedc38549e75fce3d81 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 10 Jan 2025 13:36:48 +0100 Subject: [PATCH] feat: allow arbitrary response for suggester --- src/constants/dictionary.ts | 4 +++ .../response-format-single.jsx | 6 ++++ .../response-format-table.jsx | 15 +++++++-- .../response-format-single.jsx | 5 ++- .../transformations/response-format-table.jsx | 2 ++ src/model/transformations/response.jsx | 8 +++++ src/model/transformations/response.spec.jsx | 32 +++++++++++++++++++ .../single/response-format-single.jsx | 18 ++++++++++- 8 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/constants/dictionary.ts b/src/constants/dictionary.ts index 6501fef52..5621db533 100644 --- a/src/constants/dictionary.ts +++ b/src/constants/dictionary.ts @@ -889,6 +889,10 @@ const dictionary: Dictionary = { fr: 'Retrouver dans le questionnaire', en: 'Retrieve in the questionnaire', }, + allowArbitraryResponse: { + fr: 'Autoriser une réponse libre', + en: 'Allow arbitrary response', + }, list: { fr: 'Liste', en: 'List', diff --git a/src/model/formToState/component-new-edit/response-format-single.jsx b/src/model/formToState/component-new-edit/response-format-single.jsx index 0e3441a23..ce1c00227 100644 --- a/src/model/formToState/component-new-edit/response-format-single.jsx +++ b/src/model/formToState/component-new-edit/response-format-single.jsx @@ -11,6 +11,7 @@ import { const { RADIO } = DATATYPE_VIS_HINT; export const defaultState = { + allowArbitrary: false, mandatory: false, hasSpecialCode: false, specialLabel: '', @@ -22,6 +23,7 @@ export const defaultState = { }; export const defaultForm = { + allowArbitrary: false, mandatory: false, hasSpecialCode: false, specialLabel: '', @@ -35,6 +37,7 @@ export const defaultForm = { export function formToState(form, transformers) { const { id, + allowArbitrary, mandatory, visHint, hasSpecialCode, @@ -47,6 +50,7 @@ export function formToState(form, transformers) { return { id, + allowArbitrary, mandatory, visHint, hasSpecialCode, @@ -64,6 +68,7 @@ export function formToState(form, transformers) { export function stateToForm(currentState, transformers) { const { id, + allowArbitrary, visHint, mandatory, hasSpecialCode, @@ -75,6 +80,7 @@ export function stateToForm(currentState, transformers) { return { id, + allowArbitrary, mandatory, visHint, hasSpecialCode, diff --git a/src/model/formToState/component-new-edit/response-format-table.jsx b/src/model/formToState/component-new-edit/response-format-table.jsx index a557417e2..86c5619c0 100644 --- a/src/model/formToState/component-new-edit/response-format-table.jsx +++ b/src/model/formToState/component-new-edit/response-format-table.jsx @@ -67,6 +67,7 @@ export const defaultMeasureState = { cloneDeep(CodesListDefaultState), { id: uuid() }, ), + allowArbitrary: false, visHint: RADIO, }, }; @@ -76,6 +77,7 @@ export const defaultMeasureForm = { type: SIMPLE, [SIMPLE]: defaultMeasureSimpleState, [SINGLE_CHOICE]: { + allowArbitrary: false, hasSpecialCode: false, specialLabel: '', specialCode: '', @@ -172,13 +174,17 @@ export function formToStateMeasure(form, codesListMeasure) { [simpleType]: { ...simpleForm }, }; } else { - const { visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListForm } = - measureForm; + const { + allowArbitrary, + visHint, + [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListForm, + } = measureForm; const codesList = codesListMeasure ? codesListMeasure.formToStateComponent(codesListForm) : CodesListFactory().formToState(codesListForm); state[SINGLE_CHOICE] = { + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesList, }; @@ -253,6 +259,7 @@ export function stateToFormMeasure( type, [SIMPLE]: simpleState, [SINGLE_CHOICE]: { + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListState, }, @@ -273,6 +280,7 @@ export function stateToFormMeasure( type, [SIMPLE]: simpleState, [SINGLE_CHOICE]: { + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListForm, }, @@ -282,6 +290,7 @@ export function stateToFormMeasure( export function stateToFormMeasureList(currentState, codesListsStore) { const { [SINGLE_CHOICE]: { + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListState, }, @@ -295,6 +304,7 @@ export function stateToFormMeasureList(currentState, codesListsStore) { return { ...currentState, [SINGLE_CHOICE]: { + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListForm, }, @@ -377,6 +387,7 @@ const Factory = (initialState = {}, codesListsStore) => { state[SINGLE_CHOICE] = { [DEFAULT_CODES_LIST_SELECTOR_PATH]: codesListsStore[measureState[DEFAULT_CODES_LIST_SELECTOR_PATH].id], + allowArbitrary: measureState.allowArbitrary, visHint: measureState.visHint, }; } else { diff --git a/src/model/transformations/response-format-single.jsx b/src/model/transformations/response-format-single.jsx index f0c0d3a4e..65c65c1da 100644 --- a/src/model/transformations/response-format-single.jsx +++ b/src/model/transformations/response-format-single.jsx @@ -12,7 +12,7 @@ export function remoteToState(remote) { const { responses: [ { - Datatype: { visualizationHint: visHint }, + Datatype: { allowArbitrary, visualizationHint: visHint }, mandatory, nonResponseModality, CodeListReference, @@ -26,6 +26,7 @@ export function remoteToState(remote) { CodeList.remoteToState(CodeListReference), id, mandatory, + allowArbitrary, visHint, hasSpecialCode: !!nonResponseModality, specialLabel: @@ -45,6 +46,7 @@ export function remoteToState(remote) { export function stateToRemote(state, collectedVariables) { const { [DEFAULT_CODES_LIST_SELECTOR_PATH]: { id: codesListId }, + allowArbitrary, visHint, mandatory, id, @@ -54,6 +56,7 @@ export function stateToRemote(state, collectedVariables) { Response.stateToRemote({ id, mandatory, + allowArbitrary, visHint, codesListId, typeName: TEXT, diff --git a/src/model/transformations/response-format-table.jsx b/src/model/transformations/response-format-table.jsx index 9c7e559b7..fb353d16b 100644 --- a/src/model/transformations/response-format-table.jsx +++ b/src/model/transformations/response-format-table.jsx @@ -318,6 +318,7 @@ function stateToResponseState(state) { } else { const { mandatory, + allowArbitrary, visHint, [DEFAULT_CODES_LIST_SELECTOR_PATH]: { id: codesListId }, } = measureTypeState; @@ -327,6 +328,7 @@ function stateToResponseState(state) { typeName: TEXT, maxLength: 1, pattern: '', + allowArbitrary, visHint, }; } diff --git a/src/model/transformations/response.jsx b/src/model/transformations/response.jsx index f2d90d8d8..f8ee23667 100644 --- a/src/model/transformations/response.jsx +++ b/src/model/transformations/response.jsx @@ -1,5 +1,6 @@ import { DATATYPE_TYPE_FROM_NAME, + DATATYPE_VIS_HINT, UI_BEHAVIOUR, } from '../../constants/pogues-constants'; import { uuid } from '../../utils/utils'; @@ -26,6 +27,7 @@ export function stateToRemote(state, response) { mayears: Mayears, mamonths: Mamonths, codesListId: CodeListReference, + allowArbitrary, visHint: visualizationHint, hasSpecialCode, specialLabel, @@ -55,6 +57,12 @@ export function stateToRemote(state, response) { model.CodeListReference = CodeListReference; if (mandatory !== undefined) model.mandatory = mandatory === '' ? false : mandatory; + if (allowArbitrary !== undefined) + // we keep allowArbitrary value only for suggester + model.Datatype.allowArbitrary = + visualizationHint === DATATYPE_VIS_HINT.SUGGESTER + ? allowArbitrary + : undefined; if (visualizationHint !== undefined) model.Datatype.visualizationHint = visualizationHint; if (MaxLength !== undefined) model.Datatype.MaxLength = MaxLength; diff --git a/src/model/transformations/response.spec.jsx b/src/model/transformations/response.spec.jsx index 4c1392959..9772bd54d 100644 --- a/src/model/transformations/response.spec.jsx +++ b/src/model/transformations/response.spec.jsx @@ -3,6 +3,7 @@ import { describe, expect, test } from 'vitest'; import { DATATYPE_TYPE_FROM_NAME, + DATATYPE_VIS_HINT, UI_BEHAVIOUR, } from '../../constants/pogues-constants'; import { stateToRemote } from './response'; @@ -169,6 +170,37 @@ describe('response tranformations', () => { expect(result.Datatype.Unit).toEqual(unit); }); + test('when allowArbitrary is defined', () => { + const typeName = 'TEXT'; + const allowArbitrary = true; + + // Get all values of DATATYPE_VIS_HINT except 'SUGGESTER' + const nonSuggesterVisHints = Object.values(DATATYPE_VIS_HINT).filter( + (visHint) => visHint !== DATATYPE_VIS_HINT.SUGGESTER, + ); + + const resultSuggester = stateToRemote({ + typeName, + id: '1', + visHint: DATATYPE_VIS_HINT.SUGGESTER, + allowArbitrary, + }); + + expect(resultSuggester.Datatype.allowArbitrary).toEqual(allowArbitrary); + + // Test for all non-SUGGESTER visHint values + nonSuggesterVisHints.forEach((visHint) => { + const result = stateToRemote({ + typeName, + id: '1', + visHint, + allowArbitrary, + }); + + expect(result.Datatype.allowArbitrary).toEqual(undefined); + }); + }); + test('when Format is defined', () => { const typeName = 'DATE'; const result = stateToRemote({ diff --git a/src/widgets/component-new-edit/components/response-format/single/response-format-single.jsx b/src/widgets/component-new-edit/components/response-format/single/response-format-single.jsx index ff46711de..aa35a5564 100644 --- a/src/widgets/component-new-edit/components/response-format/single/response-format-single.jsx +++ b/src/widgets/component-new-edit/components/response-format/single/response-format-single.jsx @@ -168,7 +168,22 @@ function ResponseFormatSingle({ )} {visHint === SUGGESTER ? ( - + <> + + value === 'true'} + // Convert true/false/undefined to string "true"/"false" when displaying the form + format={(value) => (value === true ? 'true' : 'false')} + > + {Dictionary.yes} + {Dictionary.no} + + ) : ( { componentsStore: state.appState.activeComponentsById, collectedVariablesStore: state.appState.collectedVariableByQuestion, visHint: selector(state, `${path}visHint`), + allowArbitrary: selector(state, `${path}allowArbitrary`), path, }; };