Skip to content

Commit

Permalink
fix: [DHIS2-16010] app crashes on invalid programid (#3765)
Browse files Browse the repository at this point in the history
* fix: app crash on invalid programid

* fix: app stuck in loading state when url has invalid enrollmentId or teiId

* fix: useProgramInfo hook breaks the app
  • Loading branch information
alaa-yahia authored Oct 22, 2024
1 parent e17834e commit 9133a63
Show file tree
Hide file tree
Showing 29 changed files with 195 additions and 215 deletions.
2 changes: 0 additions & 2 deletions cypress/e2e/ScopeSelector/ScopeSelector.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ Feature: User uses the ScopeSelector to navigate
When you select both org unit and program Malaria case registration
Then you should see the table

# DHIS2-16010 - App crashes on invalid program id
@skip
Scenario: Main page > Url with invalid program id
Given you land on a main page with an invalid program id
Then you should see error message
Expand Down
3 changes: 0 additions & 3 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -705,9 +705,6 @@ msgstr "View working list in this program."
msgid "Page is missing required values from URL"
msgstr "Page is missing required values from URL"

msgid "Program is not valid"
msgstr "Program is not valid"

msgid "Org unit is not valid with current program"
msgstr "Org unit is not valid with current program"

Expand Down
13 changes: 7 additions & 6 deletions src/core_modules/capture-core/HOC/withErrorMessageHandler.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @flow
import * as React from 'react';
import { withStyles } from '@material-ui/core/styles';
import { NoticeBox } from '@dhis2/ui';

const getStyles = (theme: Theme) => ({
const getStyles = () => ({
errorContainer: {
margin: 20,
color: theme.palette.error.main,
},
});

Expand All @@ -23,12 +23,13 @@ export const withErrorMessageHandler = () =>

if (error) {
return (
<div
data-test="error-message-handler"
<NoticeBox
error
dataTest="error-message-handler"
className={classes.errorContainer}
>
{error}
</div>
<div>{error}</div>
</NoticeBox>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LoadingMaskForPage } from '../../LoadingMasks/LoadingMaskForPage.compon
import { withErrorMessageHandler } from '../../../HOC';
import { MissingMessage } from './MissingMessage.component';
import { EnrollmentPageDefault } from './EnrollmentPageDefault';
import { TopBar } from './TopBar.container';


const getStyles = ({ typography }) => ({
loadingMask: {
Expand All @@ -22,36 +22,19 @@ const getStyles = ({ typography }) => ({

const EnrollmentPagePlain = ({
classes,
programId,
orgUnitId,
enrollmentId,
trackedEntityName,
teiDisplayName,
enrollmentPageStatus,
enrollmentsAsOptions,
}) => (
<>
<TopBar
orgUnitId={orgUnitId}
programId={programId}
trackedEntityName={trackedEntityName}
teiDisplayName={teiDisplayName}
enrollmentsAsOptions={enrollmentsAsOptions}
enrollmentId={enrollmentId}
/>

<div data-test="enrollment-page-content">
{enrollmentPageStatus === enrollmentPageStatuses.MISSING_SELECTIONS && <MissingMessage />}
<div data-test="enrollment-page-content">
{enrollmentPageStatus === enrollmentPageStatuses.MISSING_SELECTIONS && <MissingMessage />}

{enrollmentPageStatus === enrollmentPageStatuses.DEFAULT && <EnrollmentPageDefault />}
{enrollmentPageStatus === enrollmentPageStatuses.DEFAULT && <EnrollmentPageDefault />}

{enrollmentPageStatus === enrollmentPageStatuses.LOADING && (
<div className={classes.loadingMask}>
<LoadingMaskForPage />
</div>
)}
</div>
</>
{enrollmentPageStatus === enrollmentPageStatuses.LOADING && (
<div className={classes.loadingMask}>
<LoadingMaskForPage />
</div>
)}
</div>
);

export const EnrollmentPageComponent: ComponentType<$Diff<Props, CssClasses>> = compose(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
useSetEnrollmentId,
} from '../../ScopeSelector';
import { useLocationQuery } from '../../../utils/routing';
import { TopBar } from './TopBar.container';

const useComponentLifecycle = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -114,15 +115,26 @@ export const EnrollmentPage: ComponentType<{||}> = () => {
useSelector(({ activePage }) => activePage.selectionsError && activePage.selectionsError.error);

return (
<EnrollmentPageComponent
error={error}
programId={programId}
orgUnitId={orgUnitId}
enrollmentId={enrollmentId}
teiDisplayName={teiDisplayName}
trackedEntityName={trackedEntityName}
enrollmentsAsOptions={enrollmentsAsOptions}
enrollmentPageStatus={useComputedEnrollmentPageStatus()}
/>
<>
<TopBar
orgUnitId={orgUnitId}
programId={programId}
trackedEntityName={trackedEntityName}
teiDisplayName={teiDisplayName}
enrollmentsAsOptions={enrollmentsAsOptions}
enrollmentId={enrollmentId}
/>
<EnrollmentPageComponent
error={error}
programId={programId}
orgUnitId={orgUnitId}
enrollmentId={enrollmentId}
teiDisplayName={teiDisplayName}
trackedEntityName={trackedEntityName}
enrollmentsAsOptions={enrollmentsAsOptions}
enrollmentPageStatus={useComputedEnrollmentPageStatus()}
/>
</>

);
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,21 @@ export const TopBar = ({
onResetOrgUnitId={() => resetOrgUnitId()}
onStartAgain={() => reset()}
>
<SingleLockedSelect
ready={Boolean(trackedEntityName && teiDisplayName)}
onClear={() => resetTeiId('/')}
options={[
{
label: teiDisplayName,
value: 'alwaysPreselected',
},
]}
selectedValue="alwaysPreselected"
title={trackedEntityName}
displayOnly
/>
{trackedEntityName ? (
<SingleLockedSelect
ready={Boolean(trackedEntityName && teiDisplayName)}
onClear={() => resetTeiId('/')}
options={[
{
label: teiDisplayName,
value: 'alwaysPreselected',
},
]}
selectedValue="alwaysPreselected"
title={trackedEntityName}
displayOnly
/>
) : <></>}
{enrollmentsAsOptions?.length > 0 ? (
<SingleLockedSelect
ready
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @flow
import React, { useMemo } from 'react';
import React, { useMemo, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import i18n from '@dhis2/d2-i18n';
import { NoticeBox, spacersNum } from '@dhis2/ui';
import withStyles from '@material-ui/core/styles/withStyles';
import { EnrollmentAddEventPageDefault } from './EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container';
import { useLocationQuery } from '../../../utils/routing';
import { useLocationQuery, buildUrlQueryString } from '../../../utils/routing';
import {
IdTypes,
useValidatedIDsFromCache,
Expand All @@ -27,6 +28,7 @@ const styles = {
},
};
const EnrollmentAddEventPagePlain = ({ classes }: Props) => {
const history = useHistory();
const { teiId, programId, orgUnitId, enrollmentId } = useLocationQuery();
const { valid: validIds, loading, error: validatedIdsError } = useValidatedIDsFromCache({ programId, orgUnitId });
const {
Expand Down Expand Up @@ -63,6 +65,12 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => {
return EnrollmentAddEventPageStatuses.DEFAULT;
}, [enrollmentId, isLoading, loading, pageIsInvalid, programId, teiId, validIds]);

useEffect(() => {
if (pageStatus === EnrollmentAddEventPageStatuses.PROGRAM_INVALID) {
history.push(`/enrollment?${buildUrlQueryString({ orgUnitId, teiId, enrollmentId })}`);
}
}, [pageStatus, orgUnitId, teiId, enrollmentId, history]);

if (pageStatus === EnrollmentAddEventPageStatuses.LOADING) {
return <LoadingMaskForPage />;
}
Expand All @@ -89,10 +97,6 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => {
i18n.t('Page is missing required values from URL')
)}

{pageStatus === EnrollmentAddEventPageStatuses.PROGRAM_INVALID && (
i18n.t('Program is not valid')
)}

{pageStatus === EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID && (
i18n.t('Org unit is not valid with current program')
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ export const EnrollmentAddEventPageDefault = ({

const dataEntryHasChanges = useSelector(state => getDataEntryHasChanges(state, widgetReducerName));
const { program } = useProgramInfo(programId);
const selectedProgramStage = [...program.stages.values()].find(item => item.id === stageId);
const selectedProgramStage = [...program?.stages.values() ?? []].find(item => item.id === stageId);
const outputEffects = useWidgetDataFromStore(widgetReducerName);
const hideWidgets = useHideWidgetByRuleLocations(program.programRules.concat(selectedProgramStage?.programRules ?? []));
const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? []));
// $FlowFixMe
const trackedEntityName = program?.trackedEntityType?.name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
import { Program } from '../../../../metaData';

export type Props = {|
program: Program,
program: ?Program,
stageId: string,
orgUnitId: string,
teiId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const EnrollmentEditEventPageComponent = ({
mode={mode}
programStage={programStage}
enrollmentId={enrollmentId}
programId={program.id}
programId={program?.id}
enrollmentsAsOptions={enrollmentsAsOptions}
trackedEntityName={trackedEntityName}
teiDisplayName={teiDisplayName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ const EnrollmentEditEventPageWithContextPlain = ({
}, [dispatch]);

const { program } = useProgramInfo(programId);
const programStage = [...program.stages?.values()].find(item => item.id === stageId);
const hideWidgets = useHideWidgetByRuleLocations(program.programRules.concat(programStage?.programRules));
const programStage = [...program?.stages?.values() ?? []].find(item => item.id === stageId);
const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(programStage?.programRules));

const onDeleteTrackedEntitySuccess = useCallback(() => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type PlainProps = {|
enrollmentId: string,
eventId: string,
stageId: string,
program: Program,
program: ?Program,
trackedEntityTypeId: string,
mode: string,
orgUnitId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { TopBarActions } from '../../TopBarActions';
type Props = {|
programStage: ?ProgramStage,
enrollmentId: string,
programId: string,
programId: ?string,
mode: string,
orgUnitId: string,
trackedEntityName: string,
Expand Down
Loading

0 comments on commit 9133a63

Please sign in to comment.