From df0620f6aa0c757749517e0b2b790dfa8b4e70bb Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 10:29:49 +0200 Subject: [PATCH 01/12] feat: interface for accessing `organisationUnits` --- .../dataQueries/useOrganisationUnit.js | 5 +- .../organisationUnits/fetchReduxOrgUnit.js | 26 +++++++++++ .../getReduxOrgUnit.epics.js | 28 +++++++++++ .../redux/organisationUnits/index.js | 5 ++ .../organisationUnits.actions.js | 21 +++++++++ .../organisationUnits.types.js | 11 +++++ .../organisationUnits/useReduxOrgUnit.js | 46 +++++++++++++++++++ src/epics/trackerCapture.epics.js | 4 ++ 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/core_modules/capture-core/redux/organisationUnits/fetchReduxOrgUnit.js create mode 100644 src/core_modules/capture-core/redux/organisationUnits/getReduxOrgUnit.epics.js create mode 100644 src/core_modules/capture-core/redux/organisationUnits/index.js create mode 100644 src/core_modules/capture-core/redux/organisationUnits/organisationUnits.actions.js create mode 100644 src/core_modules/capture-core/redux/organisationUnits/organisationUnits.types.js create mode 100644 src/core_modules/capture-core/redux/organisationUnits/useReduxOrgUnit.js diff --git a/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js b/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js index 2760a59fd1..5472a1ccfa 100644 --- a/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js +++ b/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js @@ -4,6 +4,7 @@ import { useDataQuery } from '@dhis2/app-runtime'; import log from 'loglevel'; import { errorCreator } from '../../capture-core-utils'; +// Skips fetching if orgUnitId is falsy export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { const [orgUnit, setOrgUnit] = useState(); const { error, loading, data, refetch, called } = useDataQuery( @@ -24,7 +25,7 @@ export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { ); useEffect(() => { - refetch({ variables: { orgUnitId } }); + orgUnitId && refetch({ variables: { orgUnitId } }); }, [refetch, orgUnitId]); useEffect(() => { @@ -35,7 +36,7 @@ export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { useEffect(() => { const organisationUnit = data?.organisationUnits; - setOrgUnit( + orgUnitId && setOrgUnit( (loading || !called || error) ? undefined : { id: orgUnitId, diff --git a/src/core_modules/capture-core/redux/organisationUnits/fetchReduxOrgUnit.js b/src/core_modules/capture-core/redux/organisationUnits/fetchReduxOrgUnit.js new file mode 100644 index 0000000000..51ab968d41 --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/fetchReduxOrgUnit.js @@ -0,0 +1,26 @@ +// @flow +import { getAssociatedOrgUnitGroups } from 'capture-core/MetaDataStoreUtils/getAssociatedOrgUnitGroups'; +import type { ReduxOrgUnit } from './organisationUnits.types'; +import type { QuerySingleResource } from '../../utils/api/api.types'; + +// Builds new ReduxOrgUnit by fetching data from the api and index db +export async function fetchReduxOrgUnit( + orgUnitId: string, + querySingleResource: QuerySingleResource, +): Promise { + return Promise.all([ + querySingleResource({ + resource: `organisationUnits/${orgUnitId}`, + params: { + fields: 'displayName,code,path', + }, + }), + getAssociatedOrgUnitGroups(orgUnitId), + ]).then(([orgUnit, groups]) => ({ + id: orgUnitId, + name: orgUnit.displayName, + code: orgUnit.code, + path: orgUnit.path, + groups, + })); +} diff --git a/src/core_modules/capture-core/redux/organisationUnits/getReduxOrgUnit.epics.js b/src/core_modules/capture-core/redux/organisationUnits/getReduxOrgUnit.epics.js new file mode 100644 index 0000000000..c37d75e8fb --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/getReduxOrgUnit.epics.js @@ -0,0 +1,28 @@ +// @flow +import { ofType } from 'redux-observable'; +import { catchError, mergeMap, concatMap } from 'rxjs/operators'; +import { from, of } from 'rxjs'; +import { actionTypes, orgUnitFetched, type FetchOrgUnitPayload } from './organisationUnits.actions'; +import { fetchReduxOrgUnit } from './fetchReduxOrgUnit'; + +export const getReduxOrgUnitEpic = ( + action$: InputObservable, + store: ReduxStore, + { querySingleResource }: ApiUtils, +) => action$.pipe( + ofType(actionTypes.GET_ORGUNIT), + concatMap((action: ReduxAction) => { + const { organisationUnits } = store.value; + const payload = action.payload; + if (organisationUnits[payload.orgUnitId]) { + return of(payload.onSuccess(organisationUnits[payload.orgUnitId])); + } + return from(fetchReduxOrgUnit(payload.orgUnitId, querySingleResource)) + .pipe( + mergeMap(orgUnit => + of(orgUnitFetched(orgUnit), payload.onSuccess(orgUnit))), + catchError(error => + (payload.onError ? of(payload.onError(error)) : of({}))), + ); + }), +); diff --git a/src/core_modules/capture-core/redux/organisationUnits/index.js b/src/core_modules/capture-core/redux/organisationUnits/index.js new file mode 100644 index 0000000000..ed880346dc --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/index.js @@ -0,0 +1,5 @@ +// @flow +export { useReduxOrgUnit } from './useReduxOrgUnit'; +export { getOrgUnit } from './organisationUnits.actions'; +export { getReduxOrgUnitEpic } from './getReduxOrgUnit.epics'; +export type { ReduxOrgUnit } from './organisationUnits.types'; diff --git a/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.actions.js b/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.actions.js new file mode 100644 index 0000000000..5d7dc72165 --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.actions.js @@ -0,0 +1,21 @@ +// @flow +import { actionCreator } from '../../actions/actions.utils'; +import type { ReduxOrgUnit } from './organisationUnits.types'; + +export const actionTypes = { + GET_ORGUNIT: 'organisationUnits.GetOrgUnit', + ORG_UNIT_FETCHED: 'organisationUnits.OrgUnitFetched', +}; + +type ActionCreator = (payload: T) => ReduxAction; + +// Public +export type FetchOrgUnitPayload = { + orgUnitId: string, + onSuccess: ActionCreator, + onError?: ActionCreator, +} +export const getOrgUnit = (payload: FetchOrgUnitPayload) => actionCreator(actionTypes.GET_ORGUNIT)(payload); + +// Private +export const orgUnitFetched = (orgUnit: ReduxOrgUnit) => actionCreator(actionTypes.ORG_UNIT_FETCHED)(orgUnit); diff --git a/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.types.js b/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.types.js new file mode 100644 index 0000000000..f08257f9f6 --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/organisationUnits.types.js @@ -0,0 +1,11 @@ +// @flow +import type { OrgUnitGroup } from '@dhis2/rules-engine-javascript'; + +// Make sure rules engine OrgUnit is a subset of this! +export type ReduxOrgUnit = {| + id: string, + name: string, // this is the translated name (displayName) + code: string, + path: string, + groups: Array, +|}; diff --git a/src/core_modules/capture-core/redux/organisationUnits/useReduxOrgUnit.js b/src/core_modules/capture-core/redux/organisationUnits/useReduxOrgUnit.js new file mode 100644 index 0000000000..ba0b439d79 --- /dev/null +++ b/src/core_modules/capture-core/redux/organisationUnits/useReduxOrgUnit.js @@ -0,0 +1,46 @@ +// @flow +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { useSelector, useDispatch } from 'react-redux'; +import { useOrgUnitGroups } from 'capture-core/hooks/useOrgUnitGroups'; +import { useOrganisationUnit } from '../../dataQueries'; +import { orgUnitFetched } from './organisationUnits.actions'; +import { type ReduxOrgUnit } from './organisationUnits.types'; + +export function useReduxOrgUnit(orgUnitId: string): { + orgUnit?: ReduxOrgUnit, + error?: any, +} { + const dispatch = useDispatch(); + const reduxOrgUnit = useSelector(({ organisationUnits }) => organisationUnits && organisationUnits[orgUnitId]); + const id = reduxOrgUnit ? undefined : orgUnitId; + // These hooks do no work when id is undefined + const { orgUnit, error } = useOrganisationUnit(id, 'displayName,code,path'); + const { orgUnitGroups, error: groupError } = useOrgUnitGroups(id); + + if (reduxOrgUnit) { + return { orgUnit: reduxOrgUnit }; + } + + if (error) { + return { error: { error, errorComponent } }; + } else if (groupError) { + return { error: { groupError, errorComponent } }; + } + + if (orgUnit && orgUnitGroups) { + orgUnit.name = orgUnit.displayName; + orgUnit.groups = orgUnitGroups; + delete orgUnit.displayName; + dispatch(orgUnitFetched(orgUnit)); + return { orgUnit }; + } + + return {}; +} + +const errorComponent = ( +
+ {i18n.t('organisation unit could not be retrieved. Please try again later.')} +
+); diff --git a/src/epics/trackerCapture.epics.js b/src/epics/trackerCapture.epics.js index 6cce6ae2eb..19be4f8c00 100644 --- a/src/epics/trackerCapture.epics.js +++ b/src/epics/trackerCapture.epics.js @@ -214,6 +214,9 @@ import { import { orgUnitFetcherEpic, } from '../core_modules/capture-core/components/OrgUnitFetcher'; +import { + getReduxOrgUnitEpic, +} from '../core_modules/capture-core/redux/organisationUnits'; export const epics = combineEpics( resetProgramAfterSettingOrgUnitIfApplicableEpic, @@ -339,6 +342,7 @@ export const epics = combineEpics( navigateToEnrollmentOverviewEpic, scheduleEnrollmentEventEpic, orgUnitFetcherEpic, + getReduxOrgUnitEpic, updateTeiEpic, updateTeiSucceededEpic, updateTeiFailedEpic, From dd62c0353b16ee5b619a1ccbcf1f6b2c7575a5a0 Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 12:55:34 +0200 Subject: [PATCH 02/12] refactor: replace `useRulesEngineOrgUnit` with `useReduxOrgUnit` --- .../rules-engine/src/rulesEngine.types.js | 4 +-- .../EnrollmentRegistrationEntry.container.js | 4 +-- .../NewEventDataEntryWrapper.component.js | 4 +-- .../TeiRegistrationEntry.component.js | 11 ++++--- .../EnrollmentPageDefault.container.js | 4 +-- .../ProgramStageSelector.container.js | 4 +-- .../EventDetailsSection.component.js | 4 +-- .../WidgetEventEdit.container.js | 5 +-- src/core_modules/capture-core/hooks/index.js | 1 - .../hooks/useRulesEngineOrgUnit.js | 33 ------------------- 10 files changed, 22 insertions(+), 52 deletions(-) delete mode 100644 src/core_modules/capture-core/hooks/useRulesEngineOrgUnit.js diff --git a/packages/rules-engine/src/rulesEngine.types.js b/packages/rules-engine/src/rulesEngine.types.js index decb2154a0..3f1d422303 100644 --- a/packages/rules-engine/src/rulesEngine.types.js +++ b/packages/rules-engine/src/rulesEngine.types.js @@ -141,12 +141,12 @@ export type OrgUnitGroup = $ReadOnly<{| code: string, |}>; -export type OrgUnit = $ReadOnly<{| +export type OrgUnit = $ReadOnly<{ id: string, name: string, code: string, groups: Array, -|}>; +}>; export type RulesEngineInput = {| programRulesContainer: ProgramRulesContainer, diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js index 623a16abce..0a0d08ab78 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js @@ -6,7 +6,7 @@ import { EnrollmentRegistrationEntryComponent } from './EnrollmentRegistrationEn import type { OwnProps } from './EnrollmentRegistrationEntry.types'; import { useLifecycle } from './hooks'; import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; -import { useRulesEngineOrgUnit } from '../../../hooks'; +import { useReduxOrgUnit } from '../../../redux/organisationUnits'; import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; export const EnrollmentRegistrationEntry: ComponentType = ({ @@ -18,7 +18,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ ...passOnProps }) => { const orgUnitId = useCurrentOrgUnitInfo().id; - const { orgUnit, error } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit, error } = useReduxOrgUnit(orgUnitId); const { teiId, ready, diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/NewEventDataEntryWrapper.component.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/NewEventDataEntryWrapper.component.js index 97503017e5..e8279d39b1 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/NewEventDataEntryWrapper.component.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/NewEventDataEntryWrapper.component.js @@ -7,7 +7,7 @@ import { DataEntry } from './DataEntry/DataEntry.container'; import { EventsList } from './RecentlyAddedEventsList/RecentlyAddedEventsList.container'; import { useScopeTitleText } from '../../../../hooks/useScopeTitleText'; import { useCurrentProgramInfo } from '../../../../hooks/useCurrentProgramInfo'; -import { useRulesEngineOrgUnit } from '../../../../hooks/useRulesEngineOrgUnit'; +import { useReduxOrgUnit } from '../../../../redux/organisationUnits'; import { useLocationQuery } from '../../../../utils/routing'; import { useRulesEngine } from './useRulesEngine'; import type { PlainProps } from './NewEventDataEntryWrapper.types'; @@ -48,7 +48,7 @@ const NewEventDataEntryWrapperPlain = ({ }: PlainProps) => { const { id: programId } = useCurrentProgramInfo(); const orgUnitId = useLocationQuery().orgUnitId; - const { orgUnit, error } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit, error } = useReduxOrgUnit(orgUnitId); const rulesReady = useRulesEngine({ programId, orgUnit, formFoundation }); const titleText = useScopeTitleText(programId); diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js index 1a8e11bd4c..5f4da76b0c 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js @@ -9,6 +9,7 @@ import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; +import { useReduxOrgUnit } from '../../../redux/organisationUnits'; import type { Props, PlainProps } from './TeiRegistrationEntry.types'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; import { withSaveHandler } from '../../DataEntry'; @@ -54,7 +55,9 @@ const TeiRegistrationEntryPlain = const [showWarning, setShowWarning] = useState(false); const { scopeType } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); - const orgUnit = useCurrentOrgUnitInfo(); + const { id: orgUnitId } = useCurrentOrgUnitInfo(); + const { orgUnit } = useReduxOrgUnit(id); // [DHIS2-15814] Change this to new hook + const orgUnitName = orgUnit ? orgUnit.name : ''; const handleOnCancel = () => { if (!isUserInteractionInProgress) { @@ -68,9 +71,9 @@ const TeiRegistrationEntryPlain = const url = scopeType === scopeTypes.TRACKER_PROGRAM ? - buildUrlQueryString({ programId: selectedScopeId, orgUnitId: orgUnit.id }) + buildUrlQueryString({ programId: selectedScopeId, orgUnitId }) : - buildUrlQueryString({ orgUnitId: orgUnit.id }); + buildUrlQueryString({ orgUnitId }); return push(`/?${url}`); }; @@ -114,7 +117,7 @@ const TeiRegistrationEntryPlain = - {translatedTextWithStylesForTei(trackedEntityName.toLowerCase(), orgUnit.name)} + {translatedTextWithStylesForTei(trackedEntityName.toLowerCase(), orgUnitName)} { const history = useHistory(); const dispatch = useDispatch(); const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); - const { orgUnit, error } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit, error } = useReduxOrgUnit(orgUnitId); const program = useTrackerProgram(programId); const { diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.js index 1bd16b7a80..a4dbc54796 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.js @@ -10,7 +10,7 @@ import { useCommonEnrollmentDomainData, useRuleEffects } from '../../common/Enro import type { Props } from './ProgramStageSelector.types'; import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; import { useLocationQuery, buildUrlQueryString } from '../../../../utils/routing'; -import { useRulesEngineOrgUnit } from '../../../../hooks/useRulesEngineOrgUnit'; +import { useReduxOrgUnit } from '../../../../redux/organisationUnits'; import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; @@ -24,7 +24,7 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId isError: programError, } = useProgramFromIndexedDB(programId); - const { orgUnit } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit } = useReduxOrgUnit(orgUnitId); const programRules = useTrackerProgram(programId); const ruleEffects = useRuleEffects({ diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js index 4416248dcf..a925438658 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js @@ -11,7 +11,7 @@ import { ViewEventSectionHeader } from '../Section/ViewEventSectionHeader.compon import { EditEventDataEntry } from '../../../WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container'; import { ViewEventDataEntry } from '../../../WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.container'; import type { ProgramStage } from '../../../../metaData'; -import { useRulesEngineOrgUnit } from '../../../../hooks/useRulesEngineOrgUnit'; +import { useReduxOrgUnit } from '../../../../redux/organisationUnits'; import { NoticeBox } from '../../../NoticeBox'; const getStyles = () => ({ @@ -60,7 +60,7 @@ const EventDetailsSectionPlain = (props: Props) => { eventAccess, ...passOnProps } = props; const orgUnitId = useSelector(({ viewEventPage }) => viewEventPage.loadedValues?.orgUnit?.id); - const { orgUnit, error } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit, error } = useReduxOrgUnit(orgUnitId); if (error) { return error.errorComponent; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index 4c4196fcfe..2c621fe99a 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -6,7 +6,8 @@ import { spacersNum, Button, colors, IconEdit24, IconArrowLeft24 } from '@dhis2/ import { withStyles } from '@material-ui/core'; import i18n from '@dhis2/d2-i18n'; import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; -import { useEnrollmentEditEventPageMode, useRulesEngineOrgUnit, useAvailableProgramStages } from 'capture-core/hooks'; +import { useEnrollmentEditEventPageMode, useAvailableProgramStages } from 'capture-core/hooks'; +import { useReduxOrgUnit } from 'capture-core/redux/organisationUnits'; import type { Props } from './widgetEventEdit.types'; import { startShowEditEventDataEntry } from './WidgetEventEdit.actions'; import { Widget } from '../Widget'; @@ -59,7 +60,7 @@ export const WidgetEventEditPlain = ({ }: Props) => { const dispatch = useDispatch(); const { currentPageMode } = useEnrollmentEditEventPageMode(eventStatus); - const { orgUnit, error } = useRulesEngineOrgUnit(orgUnitId); + const { orgUnit, error } = useReduxOrgUnit(orgUnitId); const eventAccess = getProgramEventAccess(programId, programStage.id); const availableProgramStages = useAvailableProgramStages(programStage, teiId, enrollmentId, programId); diff --git a/src/core_modules/capture-core/hooks/index.js b/src/core_modules/capture-core/hooks/index.js index 1330576491..bc172ac729 100644 --- a/src/core_modules/capture-core/hooks/index.js +++ b/src/core_modules/capture-core/hooks/index.js @@ -3,7 +3,6 @@ export { useSearchOptions } from './useSearchOptions'; export { useTrackedEntityTypesWithCorrelatedPrograms } from './useTrackedEntityTypesWithCorrelatedPrograms'; export { useCurrentTrackedEntityTypeId } from './useCurrentTrackedEntityTypeId'; export { useEnrollmentEditEventPageMode } from './useEnrollmentEditEventPageMode'; -export { useRulesEngineOrgUnit } from './useRulesEngineOrgUnit'; export { useAvailableProgramStages } from './useAvailableProgramStages'; export { useScopeInfo } from './useScopeInfo'; export { useScopeTitleText } from './useScopeTitleText'; diff --git a/src/core_modules/capture-core/hooks/useRulesEngineOrgUnit.js b/src/core_modules/capture-core/hooks/useRulesEngineOrgUnit.js deleted file mode 100644 index 94f9fece17..0000000000 --- a/src/core_modules/capture-core/hooks/useRulesEngineOrgUnit.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow -import React from 'react'; -import i18n from '@dhis2/d2-i18n'; -import type { OrgUnit } from '@dhis2/rules-engine-javascript'; -import { useOrgUnitGroups } from 'capture-core/hooks/useOrgUnitGroups'; -import { useOrganisationUnit } from '../dataQueries'; - -export function useRulesEngineOrgUnit(orgUnitId: string): { - orgUnit?: OrgUnit, - error?: any, -} { - const { orgUnit, error } = useOrganisationUnit(orgUnitId, 'displayName,code'); - const { orgUnitGroups, error: groupError } = useOrgUnitGroups(orgUnitId); - - if (error) { - return { error: { error, errorComponent } }; - } else if (groupError) { - return { error: { groupError, errorComponent } }; - } - - if (orgUnit && orgUnitGroups) { - orgUnit.groups = orgUnitGroups; - return { orgUnit }; - } - - return {}; -} - -const errorComponent = ( -
- {i18n.t('organisation unit could not be retrieved. Please try again later.')} -
-); From 67c0dec1756bf199a6fd3584f3f0f397869155a3 Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 10:42:58 +0200 Subject: [PATCH 03/12] refactor: apply `getReduxOrgUnitEpic` --- .../LockedSelector/LockedSelector.actions.js | 3 +- .../LockedSelector/LockedSelector.epics.js | 32 +++++-------- .../OrgUnitFetcher/OrgUnitFetcher.actions.js | 3 +- .../OrgUnitFetcher.component.js | 8 +--- .../OrgUnitFetcher/OrgUnitFetcher.epics.js | 19 ++++---- .../Pages/ViewEvent/epics/viewEvent.epics.js | 47 +++++++++---------- .../actions/QuickSelector.actions.js | 4 -- .../organisationUnits.reducerDescription.js | 24 ++-------- .../rules/getRulesEngineOrgUnit.js | 20 -------- 9 files changed, 51 insertions(+), 109 deletions(-) delete mode 100644 src/core_modules/capture-core/rules/getRulesEngineOrgUnit.js diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js index a91ec8b952..5426d31572 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js @@ -1,5 +1,6 @@ // @flow import { actionCreator } from '../../actions/actions.utils'; +import type { ReduxOrgUnit } from '../../redux/organisationUnits'; export const lockedSelectorActionTypes = { LOADING_START: 'LockedSelector.Loading', @@ -17,7 +18,7 @@ export const lockedSelectorActionTypes = { export const updateSelectionsFromUrl = (data: Object) => actionCreator(lockedSelectorActionTypes.FROM_URL_UPDATE)(data); export const validSelectionsFromUrl = () => actionCreator(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_VALID)(); export const invalidSelectionsFromUrl = (error: string) => actionCreator(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_INVALID)({ error }); -export const setCurrentOrgUnitBasedOnUrl = (orgUnit: Object) => actionCreator(lockedSelectorActionTypes.FETCH_ORG_UNIT_SUCCESS)(orgUnit); +export const setCurrentOrgUnitBasedOnUrl = (orgUnit: ReduxOrgUnit) => actionCreator(lockedSelectorActionTypes.FETCH_ORG_UNIT_SUCCESS)(orgUnit); export const startLoading = () => actionCreator(lockedSelectorActionTypes.LOADING_START)(); export const completeUrlUpdate = () => actionCreator(lockedSelectorActionTypes.FROM_URL_UPDATE_COMPLETE)(); export const errorRetrievingOrgUnitBasedOnUrl = (error: string) => actionCreator(lockedSelectorActionTypes.FETCH_ORG_UNIT_ERROR)({ error }); diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js index ba5a8d7032..360cbd6508 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js @@ -1,8 +1,8 @@ // @flow import i18n from '@dhis2/d2-i18n'; import { ofType } from 'redux-observable'; -import { catchError, filter, flatMap, map, startWith, switchMap } from 'rxjs/operators'; -import { from, of } from 'rxjs'; +import { filter, map, concatMap } from 'rxjs/operators'; +import { of } from 'rxjs'; import { lockedSelectorActionTypes, invalidSelectionsFromUrl, @@ -16,32 +16,25 @@ import { import { programCollection } from '../../metaDataMemoryStores'; import { getLocationPathname, pageFetchesOrgUnitUsingTheOldWay } from '../../utils/url'; import { getLocationQuery } from '../../utils/routing'; +import { getOrgUnit } from '../../redux/organisationUnits'; -const orgUnitsQuery = id => ({ resource: 'organisationUnits', id }); - -export const getOrgUnitDataBasedOnUrlUpdateEpic = ( - action$: InputObservable, - store: ReduxStore, - { querySingleResource }: ApiUtils) => +export const getOrgUnitDataBasedOnUrlUpdateEpic = (action$: InputObservable, store: ReduxStore) => action$.pipe( ofType(lockedSelectorActionTypes.FROM_URL_UPDATE), filter(action => action.payload.nextProps.orgUnitId), - switchMap((action) => { + concatMap((action) => { const { organisationUnits } = store.value; const { orgUnitId } = action.payload.nextProps; if (organisationUnits[orgUnitId]) { return of(completeUrlUpdate()); } - return from(querySingleResource(orgUnitsQuery(action.payload.nextProps.orgUnitId))) - .pipe( - flatMap(response => - of(setCurrentOrgUnitBasedOnUrl({ id: response.id, name: response.displayName, code: response.code }))), - catchError(() => - of(errorRetrievingOrgUnitBasedOnUrl(i18n.t('Could not get organisation unit')))), - startWith(startLoading()), - ); - }, - )); + return of(startLoading(), getOrgUnit({ + orgUnitId, + onSuccess: setCurrentOrgUnitBasedOnUrl, + onError: () => errorRetrievingOrgUnitBasedOnUrl(i18n.t('Could not get organisation unit')), + })); + }), + ); export const setOrgUnitDataEmptyBasedOnUrlUpdateEpic = (action$: InputObservable) => action$.pipe( @@ -76,4 +69,3 @@ export const validateSelectionsBasedOnUrlUpdateEpic = (action$: InputObservable) return validSelectionsFromUrl(); })); - diff --git a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.actions.js b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.actions.js index 260ad0c4ae..4c36fd473d 100644 --- a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.actions.js +++ b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.actions.js @@ -1,6 +1,7 @@ // @flow import { actionCreator } from '../../actions/actions.utils'; +import type { ReduxOrgUnit } from '../../redux/organisationUnits'; export const actionTypes = { FETCH_ORG_UNIT: 'OrgUnitFetcher.FetchOrgUnit', @@ -11,6 +12,6 @@ export const actionTypes = { export const fetchOrgUnit = (orgUnitId: string) => actionCreator(actionTypes.FETCH_ORG_UNIT)({ orgUnitId }); -export const setCurrentOrgUnit = (orgUnit: Object) => actionCreator(actionTypes.FETCH_ORG_UNIT_SUCCESS)(orgUnit); +export const setCurrentOrgUnit = (orgUnit: ReduxOrgUnit) => actionCreator(actionTypes.FETCH_ORG_UNIT_SUCCESS)(orgUnit); export const errorRetrievingOrgUnit = () => actionCreator(actionTypes.FETCH_ORG_UNIT_FAILURE)(); diff --git a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.component.js b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.component.js index 51f7bb0d70..199c96f6ce 100644 --- a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.component.js +++ b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.component.js @@ -6,13 +6,7 @@ import { fetchOrgUnit } from './OrgUnitFetcher.actions'; export const OrgUnitFetcher = (({ orgUnitId, children, error }: Object) => { const dispatch = useDispatch(); - const { orgUnit } = useSelector( - ({ - organisationUnits, - }) => ({ - orgUnit: organisationUnits[orgUnitId], - }), - ); + const orgUnit = useSelector(({ organisationUnits }) => organisationUnits[orgUnitId]); useEffect(() => { if (!orgUnit && orgUnitId) { diff --git a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.epics.js b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.epics.js index 1c36e942d1..d50ed49f73 100644 --- a/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.epics.js +++ b/src/core_modules/capture-core/components/OrgUnitFetcher/OrgUnitFetcher.epics.js @@ -1,17 +1,14 @@ // @flow import { ofType } from 'redux-observable'; -import { catchError, map, switchMap } from 'rxjs/operators'; -import { from, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { getOrgUnit } from '../../redux/organisationUnits'; import { actionTypes, setCurrentOrgUnit, errorRetrievingOrgUnit } from './OrgUnitFetcher.actions'; - -const orgUnitsQuery = id => ({ resource: `organisationUnits/${id}` }); - -export const orgUnitFetcherEpic = (action$: InputObservable, _: ReduxStore, { querySingleResource }: ApiUtils, -) => action$.pipe( +export const orgUnitFetcherEpic = (action$: InputObservable) => action$.pipe( ofType(actionTypes.FETCH_ORG_UNIT), - switchMap(({ payload: { orgUnitId } }) => from(querySingleResource(orgUnitsQuery(orgUnitId))) - .pipe(map(({ id, displayName: name }) => setCurrentOrgUnit({ id, name })))) - , - catchError(() => of(errorRetrievingOrgUnit())), + map(({ payload: { orgUnitId } }) => getOrgUnit({ + orgUnitId, + onSuccess: orgUnit => setCurrentOrgUnit(orgUnit), + onError: errorRetrievingOrgUnit, + })), ); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js index 91c84cced6..254a6cf3bc 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/viewEvent.epics.js @@ -4,8 +4,7 @@ import { ofType } from 'redux-observable'; import { map, switchMap } from 'rxjs/operators'; import i18n from '@dhis2/d2-i18n'; import { errorCreator } from 'capture-core-utils'; -import { getRulesEngineOrgUnit } from 'capture-core/rules/getRulesEngineOrgUnit'; -import { getAssociatedOrgUnitGroups } from 'capture-core/MetaDataStoreUtils/getAssociatedOrgUnitGroups'; +import { getOrgUnit } from 'capture-core/redux/organisationUnits'; import { isSelectionsEqual } from '../../../App/isSelectionsEqual'; import { getErrorMessageAndDetails } from '../../../../utils/errors/getErrorMessageAndDetails'; @@ -39,18 +38,24 @@ export const getEventOpeningFromEventListEpic = ( action$.pipe( ofType(eventWorkingListsActionTypes.VIEW_EVENT_PAGE_OPEN), switchMap(({ payload: { eventId } }) => getEvent(eventId, absoluteApiPath, querySingleResource) - .then(eventContainer => - (eventContainer - ? Promise.all( - [eventContainer, getRulesEngineOrgUnit(eventContainer.event.orgUnitId, querySingleResource)], - ) - : [])) - .then(([eventContainer, orgUnit]) => { + .then((eventContainer) => { if (!eventContainer) { return openViewEventPageFailed( i18n.t('Event could not be loaded. Are you sure it exists?')); } - return startOpenEventForView(eventContainer, orgUnit); + return getOrgUnit({ + orgUnitId: eventContainer.event.orgUnitId, + onSuccess: orgUnit => startOpenEventForView(eventContainer, orgUnit), + onError: (error) => { + const { message, details } = getErrorMessageAndDetails(error); + log.error( + errorCreator( + message || + i18n.t('Organisation unit could not be loaded'))(details)); + return openViewEventPageFailed( + i18n.t('Could not get organisation unit')); + }, + }); }) .catch((error) => { const { message, details } = getErrorMessageAndDetails(error); @@ -95,28 +100,22 @@ export const getEventFromUrlEpic = ( }); })); -export const getOrgUnitOnUrlUpdateEpic = (action$: InputObservable, _: ReduxStore, { querySingleResource }: ApiUtils) => +export const getOrgUnitOnUrlUpdateEpic = (action$: InputObservable) => action$.pipe( ofType(viewEventActionTypes.EVENT_FROM_URL_RETRIEVED), - switchMap((action) => { + map((action) => { const eventContainer = action.payload.eventContainer; - // change from organisationUnitGroups -> groups - return Promise.all( - [ - querySingleResource({ resource: `organisationUnits/${eventContainer.event.orgUnitId}` }), - getAssociatedOrgUnitGroups(eventContainer.event.orgUnitId), - ]) - .then(([orgUnit, groups]) => { - orgUnit.groups = groups; - return orgUnitRetrievedOnUrlUpdate(orgUnit, eventContainer); - }) - .catch((error) => { + return getOrgUnit({ + orgUnitId: eventContainer.event.orgUnitId, + onSuccess: orgUnit => orgUnitRetrievedOnUrlUpdate(orgUnit, eventContainer), + onError: (error) => { const { message, details } = getErrorMessageAndDetails(error); log.error(errorCreator( message || i18n.t('Organisation unit could not be loaded'))(details)); return orgUnitCouldNotBeRetrievedOnUrlUpdate(eventContainer); - }); + }, + }); })); export const openViewPageLocationChangeEpic = (action$: InputObservable, _: ReduxStore, { history }: ApiUtils) => diff --git a/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/actions/QuickSelector.actions.js b/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/actions/QuickSelector.actions.js index 184bda91b2..214f58bb4b 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/actions/QuickSelector.actions.js +++ b/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/actions/QuickSelector.actions.js @@ -3,7 +3,6 @@ import { actionCreator } from '../../../../actions/actions.utils'; export const actionTypes = { SET_ORG_UNIT_ID: 'setOrgUnitId', - STORE_ORG_UNIT_OBJECT: 'storeOrgUnitObject', SET_PROGRAM_ID: 'setProgramId', SET_CATEGORY_ID: 'setCategoryId', RESET_CATEGORY_SELECTIONS: 'resetCategorySelections', @@ -15,9 +14,6 @@ export const actionTypes = { export const setOrgUnitId = (orgUnitId: string) => actionCreator(actionTypes.SET_ORG_UNIT_ID)(orgUnitId); -export const storeOrgUnitObject = - (orgUnitObject: Object) => actionCreator(actionTypes.STORE_ORG_UNIT_OBJECT)(orgUnitObject); - export const setProgramId = (programId: string) => actionCreator(actionTypes.SET_PROGRAM_ID)(programId); diff --git a/src/core_modules/capture-core/reducers/descriptions/organisationUnits.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/organisationUnits.reducerDescription.js index 6610eb8e40..33b4677d52 100644 --- a/src/core_modules/capture-core/reducers/descriptions/organisationUnits.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/organisationUnits.reducerDescription.js @@ -1,32 +1,14 @@ // @flow import { createReducerDescription } from '../../trackerRedux/trackerReducer'; -import { actionTypes as viewEventActionTypes } from '../../components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions'; -import { actionTypes as setOrgUnitActionTypes } from '../../components/ScopeSelector/QuickSelector/actions/QuickSelector.actions'; -import { lockedSelectorActionTypes } from '../../components/LockedSelector/LockedSelector.actions'; import { actionTypes as initActionTypes } from '../../init/init.actions'; -import { actionTypes as orgUnitFetcherActionTypes } from '../../components/OrgUnitFetcher/OrgUnitFetcher.actions'; +import { actionTypes as reduxOrgunitActionTypes } from '../../redux/organisationUnits/organisationUnits.actions'; +import type { ReduxOrgUnit } from '../../redux/organisationUnits'; export const organisationUnitDesc = createReducerDescription({ - [viewEventActionTypes.ORG_UNIT_RETRIEVED_ON_URL_UPDATE]: (state, action) => { - const newState = { ...state }; - const orgUnit = action.payload.orgUnit; - newState[orgUnit.id] = orgUnit; - return newState; - }, - [setOrgUnitActionTypes.STORE_ORG_UNIT_OBJECT]: (state, action) => { - const newState = { ...state }; - const orgUnit = action.payload; - newState[orgUnit.id] = orgUnit; - return newState; - }, - - [lockedSelectorActionTypes.FETCH_ORG_UNIT_SUCCESS]: (state, action) => ({ + [reduxOrgunitActionTypes.ORG_UNIT_FETCHED]: (state: ReduxState, action: { payload: ReduxOrgUnit }) => ({ ...state, [action.payload.id]: action.payload, }), - [orgUnitFetcherActionTypes.FETCH_ORG_UNIT_SUCCESS]: (state, action) => ({ - ...state, [action.payload.id]: action.payload, - }), }, 'organisationUnits'); export const organisationUnitRootsDesc = createReducerDescription({ diff --git a/src/core_modules/capture-core/rules/getRulesEngineOrgUnit.js b/src/core_modules/capture-core/rules/getRulesEngineOrgUnit.js deleted file mode 100644 index d195283079..0000000000 --- a/src/core_modules/capture-core/rules/getRulesEngineOrgUnit.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow -import { getAssociatedOrgUnitGroups } from 'capture-core/MetaDataStoreUtils/getAssociatedOrgUnitGroups'; -import type { QuerySingleResource } from '../utils/api/api.types'; - -export async function getRulesEngineOrgUnit(orgUnitId: string, querySingleResource: QuerySingleResource) { - return Promise.all([ - querySingleResource({ - resource: `organisationUnits/${orgUnitId}`, - params: { - fields: 'displayName,code', - }, - }), - getAssociatedOrgUnitGroups(orgUnitId), - ]).then(([orgUnit, groups]) => ({ - id: orgUnitId, - name: orgUnit.displayName, - code: orgUnit.code, - groups, - })); -} From 5b29d3c59a02769ab7489e2df7d199b23853d280 Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 12:20:07 +0200 Subject: [PATCH 04/12] refactor: rename `useCurrentOrgUnitInfo` -> `useCurrentOrgUnitId` --- .../EnrollmentRegistrationEntry.container.js | 4 ++-- .../TeiRegistrationEntry.component.js | 7 +++---- .../TeiRegistrationEntry.container.js | 4 ++-- ...rackedEntityInstanceDataEntry.component.js | 8 ++++---- .../PossibleDuplicatesDialog/useDuplicates.js | 4 ++-- .../capture-core/hooks/useCurrentOrgUnitId.js | 5 +++++ .../hooks/useCurrentOrgUnitInfo.js | 19 ------------------- 7 files changed, 18 insertions(+), 33 deletions(-) create mode 100644 src/core_modules/capture-core/hooks/useCurrentOrgUnitId.js delete mode 100644 src/core_modules/capture-core/hooks/useCurrentOrgUnitInfo.js diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js index 0a0d08ab78..67482a7521 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'; import { EnrollmentRegistrationEntryComponent } from './EnrollmentRegistrationEntry.component'; import type { OwnProps } from './EnrollmentRegistrationEntry.types'; import { useLifecycle } from './hooks'; -import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; +import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; import { useReduxOrgUnit } from '../../../redux/organisationUnits'; import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; @@ -17,7 +17,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ onSave, ...passOnProps }) => { - const orgUnitId = useCurrentOrgUnitInfo().id; + const orgUnitId = useCurrentOrgUnitId(); const { orgUnit, error } = useReduxOrgUnit(orgUnitId); const { teiId, diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js index 5f4da76b0c..9a1674bd2e 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js @@ -8,7 +8,7 @@ import { useHistory } from 'react-router-dom'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; -import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; +import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; import { useReduxOrgUnit } from '../../../redux/organisationUnits'; import type { Props, PlainProps } from './TeiRegistrationEntry.types'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; @@ -55,7 +55,7 @@ const TeiRegistrationEntryPlain = const [showWarning, setShowWarning] = useState(false); const { scopeType } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); - const { id: orgUnitId } = useCurrentOrgUnitInfo(); + const orgUnitId = useCurrentOrgUnitId(); const { orgUnit } = useReduxOrgUnit(id); // [DHIS2-15814] Change this to new hook const orgUnitName = orgUnit ? orgUnit.name : ''; @@ -82,9 +82,8 @@ const TeiRegistrationEntryPlain = { scopeType === scopeTypes.TRACKED_ENTITY_TYPE && formId && <> - {/* $FlowFixMe */} { const dispatch = useDispatch(); const { scopeType, trackedEntityName } = useScopeInfo(selectedScopeId); - const { id: selectedOrgUnitId } = useCurrentOrgUnitInfo(); + const selectedOrgUnitId = useCurrentOrgUnitId(); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); const formValues = useFormValuesFromSearchTerms(); const registrationFormReady = !!formId; diff --git a/src/core_modules/capture-core/components/DataEntries/TrackedEntityInstance/TrackedEntityInstanceDataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TrackedEntityInstance/TrackedEntityInstanceDataEntry.component.js index 21a4b64e15..fad0f62845 100644 --- a/src/core_modules/capture-core/components/DataEntries/TrackedEntityInstance/TrackedEntityInstanceDataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TrackedEntityInstance/TrackedEntityInstanceDataEntry.component.js @@ -42,7 +42,7 @@ class PreTeiDataEntryPure extends React.PureComponent { } type PreTeiDataEntryProps = { - orgUnit: Object, + orgUnitId: string, trackedEntityTypeId: string, onUpdateField: Function, onStartAsyncUpdateField: Function, @@ -52,17 +52,17 @@ type PreTeiDataEntryProps = { export class PreTeiDataEntry extends React.Component { getValidationContext = () => { - const { orgUnit, onGetUnsavedAttributeValues, trackedEntityTypeId } = this.props; + const { orgUnitId, onGetUnsavedAttributeValues, trackedEntityTypeId } = this.props; return { trackedEntityTypeId, - orgUnitId: orgUnit.id, + orgUnitId, onGetUnsavedAttributeValues, }; } render() { const { - orgUnit, + orgUnitId, trackedEntityTypeId, onUpdateField, onStartAsyncUpdateField, diff --git a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/useDuplicates.js b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/useDuplicates.js index 049dfa7a50..15f7035c2c 100644 --- a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/useDuplicates.js +++ b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/useDuplicates.js @@ -1,7 +1,7 @@ // @flow import { useDispatch } from 'react-redux'; import { useCallback, useContext } from 'react'; -import { useCurrentOrgUnitInfo } from '../../hooks/useCurrentOrgUnitInfo'; +import { useCurrentOrgUnitId } from '../../hooks/useCurrentOrgUnitId'; import { changePage, reviewDuplicates } from './possibleDuplicatesDialog.actions'; import { ResultsPageSizeContext } from '../Pages/shared-contexts'; import { useScopeInfo } from '../../hooks/useScopeInfo'; @@ -10,7 +10,7 @@ export const useDuplicates = (dataEntryId: string, selectedScopeId: string) => { const { resultsPageSize } = useContext(ResultsPageSizeContext); const dispatch = useDispatch(); const { scopeType } = useScopeInfo(selectedScopeId); - const { id: orgUnitId } = useCurrentOrgUnitInfo(); + const orgUnitId = useCurrentOrgUnitId(); const dispatchOnReviewDuplicates = useCallback( () => { dispatch( diff --git a/src/core_modules/capture-core/hooks/useCurrentOrgUnitId.js b/src/core_modules/capture-core/hooks/useCurrentOrgUnitId.js new file mode 100644 index 0000000000..ada2e99463 --- /dev/null +++ b/src/core_modules/capture-core/hooks/useCurrentOrgUnitId.js @@ -0,0 +1,5 @@ +// @flow +import { useSelector } from 'react-redux'; + +export const useCurrentOrgUnitId = () => + useSelector(({ currentSelections: { orgUnitId } }) => orgUnitId); diff --git a/src/core_modules/capture-core/hooks/useCurrentOrgUnitInfo.js b/src/core_modules/capture-core/hooks/useCurrentOrgUnitInfo.js deleted file mode 100644 index 35eff69d72..0000000000 --- a/src/core_modules/capture-core/hooks/useCurrentOrgUnitInfo.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import { useSelector } from 'react-redux'; - -export const useCurrentOrgUnitInfo = (): {| id: string, name: string, code: string |} => - ({ - id: useSelector(({ currentSelections: { orgUnitId } }) => orgUnitId), - name: useSelector(( - { - organisationUnits, - currentSelections: { orgUnitId }, - }) => - organisationUnits[orgUnitId] && organisationUnits[orgUnitId].name), - code: useSelector(( - { - organisationUnits, - currentSelections: { orgUnitId }, - }) => - organisationUnits[orgUnitId] && organisationUnits[orgUnitId].code), - }); From 1091e87ccf8e4dca3c5788241a5f2178ae7f0c69 Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 10:49:48 +0200 Subject: [PATCH 05/12] fix: delay displaying the edit event button Or else cypress will click the button too soon --- .../WidgetEventEdit/WidgetEventEdit.container.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index 2c621fe99a..85c76f538c 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -1,7 +1,7 @@ // @flow import React, { type ComponentType } from 'react'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { spacersNum, Button, colors, IconEdit24, IconArrowLeft24 } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; import i18n from '@dhis2/d2-i18n'; @@ -13,6 +13,7 @@ import { startShowEditEventDataEntry } from './WidgetEventEdit.actions'; import { Widget } from '../Widget'; import { EditEventDataEntry } from './EditEventDataEntry/'; import { ViewEventDataEntry } from './ViewEventDataEntry/'; +import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NonBundledDhis2Icon } from '../NonBundledDhis2Icon'; import { getProgramEventAccess } from '../../metaData'; import { useCategoryCombinations } from '../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; @@ -61,6 +62,8 @@ export const WidgetEventEditPlain = ({ const dispatch = useDispatch(); const { currentPageMode } = useEnrollmentEditEventPageMode(eventStatus); const { orgUnit, error } = useReduxOrgUnit(orgUnitId); + // "Edit event"-button depends on loadedValues. Delay rendering component until loadedValues has been initialized. + const loadedValues = useSelector(({ viewEventPage }) => viewEventPage.loadedValues); const eventAccess = getProgramEventAccess(programId, programStage.id); const availableProgramStages = useAvailableProgramStages(programStage, teiId, enrollmentId, programId); @@ -69,7 +72,7 @@ export const WidgetEventEditPlain = ({ return error.errorComponent; } - return orgUnit ? ( + return orgUnit && loadedValues ? (
- ) : null; + ) : ; }; export const WidgetEventEdit: ComponentType<$Diff> = withStyles(styles)(WidgetEventEditPlain); From 4e014a9c2495bb7ecc3ec6f02721795bc8df4eef Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Tue, 17 Oct 2023 10:50:28 +0200 Subject: [PATCH 06/12] refactor: fix flow syntax highlighting in vscode --- .../SingleLockedSelect/SingleLockedSelect.component.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core_modules/capture-core/components/ScopeSelector/SingleLockedSelect/SingleLockedSelect.component.js b/src/core_modules/capture-core/components/ScopeSelector/SingleLockedSelect/SingleLockedSelect.component.js index 46ac687c8c..6876a09fc9 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/SingleLockedSelect/SingleLockedSelect.component.js +++ b/src/core_modules/capture-core/components/ScopeSelector/SingleLockedSelect/SingleLockedSelect.component.js @@ -14,7 +14,7 @@ import type { Icon } from '../../../metaData'; type Props = {| isUserInteractionInProgress?: boolean, - options: Array<{| label: string, value: any, icon?: Icon |}>, + options: Array