Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [DHIS2-16010] app crashes on invalid programid #3765

Merged
merged 9 commits into from
Oct 22, 2024
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 @@ -762,9 +762,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
Loading