From 53902817ac1b34688a76e30459a3c9c6c9ddf5d8 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Thu, 16 Nov 2023 10:44:54 +0100 Subject: [PATCH] feat: add inheritable TEAs --- .../hooks/useFormValues.js | 7 +-- .../SingleOrgUnitSelectField.component.js | 6 +-- .../DataEntryEnrollment.component.js | 4 ++ .../Enrollment/dataEntryEnrollment.types.js | 3 ++ .../RegisterTeiDataEntry.component.js | 8 +++- .../RegisterTei/RegisterTei.component.js | 4 ++ .../RegisterTei/RegisterTei.container.js | 15 ++++++ .../RegisterTei/RegisterTei.types.js | 5 ++ ...kedEntityRelationshipsWrapper.component.js | 1 + .../useInheritedAttributeValues.js | 47 +++++++++++++++++++ .../metaData/DataElement/DataElement.js | 9 ++++ .../factory/enrollment/DataElementFactory.js | 1 + .../TrackedEntityType/DataElementFactory.js | 1 + .../storeTrackedEntityAttributes.js | 2 +- .../storageControllers/cache.types.js | 1 + 15 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js index 1431a10e23..d84c5d278d 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js @@ -50,7 +50,7 @@ type StaticPatternValues = { const useClientAttributesWithSubvalues = (program: InputProgramData, attributes: Array) => { const dataEngine = useDataEngine(); - const [listAttributes, setListAttributes] = useState([]); + const [listAttributes, setListAttributes] = useState(null); const getListAttributes = useCallback(async () => { if (program && attributes) { @@ -139,8 +139,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni const formValuesReadyRef = useRef(false); const [formValues, setFormValues] = useState({}); const [clientValues, setClientValues] = useState({}); - const areAttributesWithSubvaluesReady = - (teiId && clientAttributesWithSubvalues.length > 0) || (!teiId && clientAttributesWithSubvalues.length === 0); useEffect(() => { formValuesReadyRef.current = false; @@ -152,7 +150,7 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni formFoundation && Object.entries(formFoundation).length > 0 && formValuesReadyRef.current === false && - areAttributesWithSubvaluesReady + !!clientAttributesWithSubvalues ) { const staticPatternValues = { orgUnitCode: orgUnit.code }; const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); @@ -172,7 +170,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni clientAttributesWithSubvalues, formValuesReadyRef, orgUnit, - areAttributesWithSubvaluesReady, searchTerms, dataEngine, ]); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js index 6c252f087b..7946993a79 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js @@ -13,7 +13,7 @@ const getStyles = () => ({ type OrgUnitValue = { id: string, - name: string, + displayName: string, path: string, } @@ -44,7 +44,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { const { classes } = this.props; return (
- {selectedOrgUnit.name} + {selectedOrgUnit.displayName}
); } @@ -52,7 +52,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { onSelectOrgUnit = (orgUnit: Object) => { this.props.onBlur({ id: orgUnit.id, - name: orgUnit.displayName, + displayName: orgUnit.displayName, path: orgUnit.path, }); } diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 2dbf0c7a20..02b607df90 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -12,6 +12,8 @@ const NewEnrollmentRelationshipPlain = theme, onSave, programId, + originTeiId, + inheritedAttributes, orgUnitId, duplicatesReviewPageSize, renderDuplicatesDialogActions, @@ -35,6 +37,8 @@ const NewEnrollmentRelationshipPlain = renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} + teiId={originTeiId} + trackedEntityInstanceAttributes={inheritedAttributes} /> ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js index de3f6cee6f..745929f7e3 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js @@ -6,11 +6,14 @@ import type { SaveForEnrollmentAndTeiRegistration, ExistingUniqueValueDialogActionsComponent, } from '../../../../../../DataEntries'; +import type { InputAttribute } from '../../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; export type Props = {| theme: Theme, programId: string, orgUnitId: string, + originTeiId: ?string, + inheritedAttributes: Array, enrollmentMetadata?: Enrollment, onSave: SaveForEnrollmentAndTeiRegistration, duplicatesReviewPageSize: number, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js index 292a1d1530..940aa904fd 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js @@ -12,7 +12,13 @@ type Props = { export class RegisterTeiDataEntryComponent extends React.Component { render() { - const { showDataEntry, programId, onSaveWithoutEnrollment, onSaveWithEnrollment, ...passOnProps } = this.props; + const { + showDataEntry, + programId, + onSaveWithoutEnrollment, + onSaveWithEnrollment, + ...passOnProps + } = this.props; if (!showDataEntry) { return null; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js index 9a9a7070af..f04efc61c2 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js @@ -58,12 +58,14 @@ const DialogButtons = ({ onCancel, onSave, trackedEntityName }) => ( const RegisterTeiPlain = ({ dataEntryId, onLink, + originTeiId, onSaveWithoutEnrollment, onSaveWithEnrollment, onGetUnsavedAttributeValues, trackedEntityName, trackedEntityTypeId, selectedScopeId, + inheritedAttributes, classes, }: ComponentProps) => { const { resultsPageSize } = useContext(ResultsPageSizeContext); @@ -110,6 +112,8 @@ const RegisterTeiPlain = ({ renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} + originTeiId={originTeiId} + inheritedAttributes={inheritedAttributes} /> { @@ -16,6 +18,17 @@ export const RegisterTei = ({ const error = useSelector(({ newRelationshipRegisterTei }) => (newRelationshipRegisterTei.error)); const selectedScopeId = suggestedProgramId || trackedEntityTypeId; const { trackedEntityName } = useScopeInfo(selectedScopeId); + const { + inheritedAttributes, + originTeiId, + isLoading: isLoadingAttributes } = useInheritedAttributeValues({ + teiId, + trackedEntityTypeId, + }); + + if (isLoadingAttributes) { + return null; + } return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js index 0de2b570ba..2d19530459 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js @@ -1,4 +1,6 @@ // @flow +import type { InputAttribute } from '../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; + export type SharedProps = {| onLink: (teiId: string, values: Object) => void, onGetUnsavedAttributeValues?: ?Function, @@ -7,6 +9,7 @@ export type SharedProps = {| export type ContainerProps = {| suggestedProgramId: string, + teiId: string, onSave: (teiPayload: Object) => void, ...SharedProps, |}; @@ -16,6 +19,8 @@ export type ComponentProps = {| error: string, dataEntryId: string, trackedEntityName: ?string, + originTeiId: ?string, + inheritedAttributes: Array, onSaveWithEnrollment: () => void, onSaveWithoutEnrollment: () => void, ...SharedProps, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js index df27fb1125..135dafe53f 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js @@ -76,6 +76,7 @@ export const TrackedEntityRelationshipsWrapper = ({ suggestedProgramId={suggestedProgramId} onLink={onLinkToTrackedEntityFromSearch} onSave={onLinkToTrackedEntityFromRegistration} + teiId={teiId} onGetUnsavedAttributeValues={() => console.log('get unsaved')} trackedEntityTypeId={selectedTrackedEntityTypeId} /> diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js new file mode 100644 index 0000000000..b8fd418738 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -0,0 +1,47 @@ +// @flow +import type { InputAttribute } from '../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; +import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; +import { getTrackedEntityTypeThrowIfNotFound } from '../../../../metaData'; + +type Props = { + teiId: string, + trackedEntityTypeId: string, +}; + +type Return = { + inheritedAttributes: Array, + originTeiId: ?string, + isLoading: boolean, +}; +export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { + const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); + const inheritedAttributeIds = trackedEntityType.attributes.reduce((acc, attribute) => { + if (attribute.inherit) { + acc.push(attribute.id); + } + return acc; + }, []); + + const { data, isLoading } = useApiDataQuery( + ['inheritedAttributeValues', teiId], + { + resource: 'tracker/trackedEntities', + id: teiId, + params: { + fields: ['attributes'], + }, + }, { + enabled: !!teiId, + select: (response) => { + const attributes = response.attributes || []; + return attributes + .filter(attribute => inheritedAttributeIds.includes(attribute.attribute)); + }, + }); + + return { + inheritedAttributes: data ?? [], + originTeiId: null, + isLoading, + }; +}; diff --git a/src/core_modules/capture-core/metaData/DataElement/DataElement.js b/src/core_modules/capture-core/metaData/DataElement/DataElement.js index 5555d4842e..78fe1d959a 100644 --- a/src/core_modules/capture-core/metaData/DataElement/DataElement.js +++ b/src/core_modules/capture-core/metaData/DataElement/DataElement.js @@ -35,6 +35,7 @@ export class DataElement { _displayInReports: boolean; _icon: Icon | void; _unique: ?Unique; + _inherit: boolean; _searchable: ?boolean; _url: ?string; _attributeValues: Array @@ -157,6 +158,14 @@ export class DataElement { return this._unique; } + get inherit(): boolean { + return this._inherit; + } + + set inherit(value: boolean) { + this._inherit = value; + } + set searchable(searchable: boolean) { this._searchable = searchable; } diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js index 18e56e1869..27d1222735 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js @@ -164,6 +164,7 @@ export class DataElementFactory { dataElement.compulsory = cachedProgramTrackedEntityAttribute.mandatory; dataElement.code = cachedTrackedEntityAttribute.code; dataElement.attributeValues = cachedTrackedEntityAttribute.attributeValues; + dataElement.inherit = cachedTrackedEntityAttribute.inherit; dataElement.name = this._getAttributeTranslation( cachedTrackedEntityAttribute.translations, diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js index bc27da80ed..723d148d3c 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js @@ -112,6 +112,7 @@ export class DataElementFactory { cachedAttribute.translations, DataElementFactory.translationPropertyNames.DESCRIPTION) || cachedAttribute.description; o.displayInForms = true; + o.inherit = cachedAttribute.inherit; o.displayInReports = cachedTrackedEntityTypeAttribute.displayInList; o.disabled = false; o.type = cachedAttribute.valueType; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js index 8624bb577e..7658ff4a8b 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js +++ b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js @@ -7,7 +7,7 @@ export const storeTrackedEntityAttributes = (ids: Array) => { resource: 'trackedEntityAttributes', params: { fields: 'id,displayName,displayShortName,displayFormName,description,valueType,optionSetValue,unique,orgunitScope,' + - 'pattern,code,attributeValues,translations[property,locale,value],optionSet[id]', + 'pattern,code,attributeValues,inherit,translations[property,locale,value],optionSet[id]', filter: `id:in:[${ids.join(',')}]`, pageSize: ids.length, }, diff --git a/src/core_modules/capture-core/storageControllers/cache.types.js b/src/core_modules/capture-core/storageControllers/cache.types.js index 49ce4a64ca..01ba461309 100644 --- a/src/core_modules/capture-core/storageControllers/cache.types.js +++ b/src/core_modules/capture-core/storageControllers/cache.types.js @@ -26,6 +26,7 @@ export type CachedTrackedEntityAttribute = { translations: Array, valueType: string, optionSetValue: boolean, + inherit: boolean, optionSet: { id: string }, unique: ?boolean, orgunitScope: ?boolean,