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-16318] enrollment page url management #3522

Merged
merged 20 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bb54962
refactor: epics for managing enrollmentId, teiId, programId, orgUnitId
superskip Jan 27, 2024
89aa653
fix: error handling during direct manipulation of url
superskip Jan 31, 2024
3242a13
fix: use verification pattern on enrollment fetch query
superskip Feb 1, 2024
c7088cc
refactor: remove unused action creators and action types
superskip Feb 1, 2024
4c3a027
fix: handle removal of enrollmentId from url
superskip Feb 2, 2024
b379617
fix: manage error messages due to failed data fetches
superskip Feb 2, 2024
3f24759
fix: handle removal of teiId from url
superskip Feb 2, 2024
a0bd69a
style: fix linter errors
superskip Feb 2, 2024
329448f
test: reintroduce breaking the glass test
superskip Feb 2, 2024
4a9a175
fix: add extra checks to make sure teiId was indeed removed from the url
superskip Feb 2, 2024
85694b8
fix: make sure the enrollment page is open before modifying the url
superskip Feb 3, 2024
62d82ba
Merge branch 'master' into DHIS2-16318
superskip Feb 5, 2024
ac914a7
fix: change enrollment id error message
superskip Feb 14, 2024
b518401
fix: hide scope selector loading spinner on orgunit id error
superskip Feb 14, 2024
bd33e74
test: auto-select orgunit
superskip Feb 14, 2024
351aa27
fix: using `useResetTeiId` causes inconvenient rerenders
superskip Feb 14, 2024
6506639
fix: invalid enrollment id error message
superskip Feb 14, 2024
a18ee9f
Merge branch 'master' into DHIS2-16318
superskip Feb 14, 2024
0c50e51
fix: remove orgunit name from scope selector on `orgUnitId` error
superskip Feb 19, 2024
04d88ff
Merge branch 'master' into DHIS2-16318
superskip Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions cypress/e2e/EnrollmentPage/BreakingTheGlass.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
Feature: Breaking the glass page

# TODO - Flaky tests should be fixed by TECH-1662
@skip
Scenario: User with search scope access tries to access an enrollment in a protected program
Given the tei created by this test is cleared from the database
And the data store is clean
Expand Down
4 changes: 4 additions & 0 deletions cypress/e2e/EnrollmentPage/EnrollmentPageNavigation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Feature: User interacts with Enrollment page
Given you enter enrollment page by typing: #/enrollment?programId=IpHINAT79UW&orgUnitId=DiszpKrYNg8&teiId=pybd813kIWx&enrollmentId=AUTO
Then you should be redirect to #/enrollment?enrollmentId=FS085BEkJo2&orgUnitId=DiszpKrYNg8&programId=IpHINAT79UW&teiId=pybd813kIWx

Scenario: Auto select orgunit when opening enrollment dashboard
Given you enter enrollment page by typing: #/enrollment?enrollmentId=avqvQMtX8DG&orgUnitId=DiszpKrYNg8&programId=IpHINAT79UW&teiId=btoHJM9byeF
Then you should be redirect to #/enrollment?enrollmentId=avqvQMtX8DG&orgUnitId=NnGUNkc5Zq8&programId=IpHINAT79UW&teiId=btoHJM9byeF

Scenario: Reset tei
Given you land on the enrollment page by having typed only the enrollmentId in the url
When you reset the tei selection
Expand Down
11 changes: 7 additions & 4 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-02-09T11:22:44.980Z\n"
"PO-Revision-Date: 2024-02-09T11:22:44.980Z\n"
"POT-Creation-Date: 2024-02-14T10:01:26.732Z\n"
"PO-Revision-Date: 2024-02-14T10:01:26.732Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -662,11 +662,14 @@ msgstr "Stop using new Enrollment dashboard for {{programName}}"
msgid "Opt out for {{programName}}"
msgstr "Opt out for {{programName}}"

msgid "Enrollment with id \"{{enrollmentId}}\" does not exist"
msgstr "Enrollment with id \"{{enrollmentId}}\" does not exist"

msgid "Tracked entity instance with id \"{{teiId}}\" does not exist"
msgstr "Tracked entity instance with id \"{{teiId}}\" does not exist"

msgid "There is an error while opening this enrollment. Please enter a valid url."
msgstr "There is an error while opening this enrollment. Please enter a valid url."
msgid "Program with id \"{{programId}}\" does not exist"
msgstr "Program with id \"{{programId}}\" does not exist"

msgid "An error occurred while fetching enrollments. Please enter a valid url."
msgstr "An error occurred while fetching enrollments. Please enter a valid url."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,137 @@
import { actionCreator } from '../../../actions/actions.utils';

export const enrollmentPageActionTypes = {
INFORMATION_FETCH: 'EnrollmentPage.Fetch',
INFORMATION_USING_TEI_ID_FETCH: 'EnrollmentPage.StartFetchingUsingTeiId',
INFORMATION_USING_ENROLLMENT_ID_FETCH: 'EnrollmentPage.StartFetchingUsingEnrollmentId',
INFORMATION_LOADING_FETCH: 'EnrollmentPage.LoadingOnFetching',
INFORMATION_ERROR_FETCH: 'EnrollmentPage.ErrorOnFetching',
INFORMATION_SUCCESS_FETCH: 'EnrollmentPage.SuccessOnFetching',

ENROLLMENTS_FETCH: 'EnrollmentPage.EnrollmentFetch',
ENROLLMENTS_ERROR_FETCH: 'EnrollmentPage.EnrollmentFetchFailure',
ENROLLMENTS_SUCCESS_FETCH: 'EnrollmentPage.EnrollmentFetchSuccess',

PAGE_OPEN: 'EnrollmentPage.Open',
PAGE_CLEAN: 'EnrollmentPage.CleanOnUnmount',
CUSTOM_PROGRAM_RESET: 'EnrollmentPage.CustomProgramReset',
PAGE_CLOSE: 'EnrollmentPage.Close',

PROCESS_ENROLLMENT_ID: 'EnrollmentPage.EnrollmentUrlIdUpdated',
RESET_ENROLLMENT_ID: 'EnrollmentPage.ResetEnrollmentId',
FETCH_ENROLLMENT_ID: 'EnrollmentPage.FetchEnrollmentId',
VERIFY_ENROLLMENT_ID_SUCCESS: 'EnrollmentPage.VerifyEnrollmentIdSuccess',
FETCH_ENROLLMENT_ID_SUCCESS: 'EnrollmentPage.FetchEnrollmentIdSuccess',
FETCH_ENROLLMENT_ID_ERROR: 'EnrollmentPage.FetchEnrollmentIdError',

PROCESS_TEI_ID: 'EnrollmentPage.TeiUrlIdUpdated',
RESET_TEI_ID: 'EnrollmentPage.ResetTeiId',
FETCH_TEI: 'EnrollmentPage.FetchTei',
VERIFY_FETCH_TEI_SUCCESS: 'EnrollmentPage.VerifyFetchTeiSuccess',
FETCH_TEI_SUCCESS: 'EnrollmentPage.FetchTeiSuccess',
FETCH_TEI_ERROR: 'EnrollmentPage.FetchTeiError',

PROCESS_PROGRAM_ID: 'EnrollmentPage.ProgramUrlIdUpdated',
COMMIT_TRACKER_PROGRAM_ID: 'EnrollmentPage.CommitTrackerProgramId',
COMMIT_NON_TRACKER_PROGRAM_ID: 'EnrollmentPage.CommitNonTrackerProgramId',
PROGRAM_ID_ERROR: 'EnrollmentPage.ProgramIdError',

FETCH_ENROLLMENTS: 'EnrollmentPage.FetchEnrollments',
VERIFY_FETCHED_ENROLLMENTS: 'EnrollmentPage.VerifyFetchedEnrollments',
FETCH_ENROLLMENTS_ERROR: 'EnrollmentPage.FetchEnrollmentsError',
FETCH_ENROLLMENTS_SUCCESS: 'EnrollmentPage.FetchEnrollmentsSuccess',

DEFAULT_VIEW: 'EnrollmentPage.DefaultView',
LOADING_VIEW: 'EnrollmentPage.LoadingView',
MISSING_MESSAGE_VIEW: 'EnrollmentPage.MissingMessageView',
ERROR_VIEW: 'EnrollmentPage.ErrorView',
CLEAR_ERROR_VIEW: 'EnrollmentPage.ClearErrorView',

DELETE_ENROLLMENT: 'EnrollmentPage.DeleteEnrollment',
UPDATE_TEI_DISPLAY_NAME: 'EnrollmentPage.UpdateTeiDisplayName',
UPDATE_ENROLLMENT_DATE: 'EnrollmentPage.UpdateEnrollmentDate',
};

export const fetchEnrollmentPageInformation = () =>
actionCreator(enrollmentPageActionTypes.INFORMATION_FETCH)();

export const startFetchingTeiFromTeiId = () =>
actionCreator(enrollmentPageActionTypes.INFORMATION_USING_TEI_ID_FETCH)();
type IdSuite = {
teiId?: ?string,
programId?: ?string,
};

export const startFetchingTeiFromEnrollmentId = () =>
actionCreator(enrollmentPageActionTypes.INFORMATION_USING_ENROLLMENT_ID_FETCH)();
export const openEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.PAGE_OPEN)();

export const showLoadingViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.INFORMATION_LOADING_FETCH)();
export const closeEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.PAGE_CLOSE)();

export const showDefaultViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.DEFAULT_VIEW)();
// enrollmentId
export const changedEnrollmentId = (enrollmentId: ?string) =>
actionCreator(enrollmentPageActionTypes.PROCESS_ENROLLMENT_ID)(enrollmentId);

export const showMissingMessageViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.MISSING_MESSAGE_VIEW)();
export const resetEnrollmentId = (payload: IdSuite) =>
actionCreator(enrollmentPageActionTypes.RESET_ENROLLMENT_ID)(payload);

export const showErrorViewOnEnrollmentPage = ({ error }: { error: string }) =>
actionCreator(enrollmentPageActionTypes.INFORMATION_ERROR_FETCH)({ error });
export const fetchEnrollmentId = (enrollmentId: string) =>
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENT_ID)({ enrollmentId });

export const verifyEnrollmentIdSuccess = ({ enrollmentId, trackedEntity, program }: Object) =>
actionCreator(enrollmentPageActionTypes.VERIFY_ENROLLMENT_ID_SUCCESS)({ enrollmentId, teiId: trackedEntity, programId: program });

export const fetchEnrollmentIdSuccess = (payload: IdSuite) =>
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENT_ID_SUCCESS)(payload);

export const fetchEnrollmentIdError = (enrollmentId: string) =>
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENT_ID_ERROR)({ enrollmentId });

// teiId
export const changedTeiId = (payload: IdSuite) =>
actionCreator(enrollmentPageActionTypes.PROCESS_TEI_ID)(payload);

export const resetTeiId = () =>
actionCreator(enrollmentPageActionTypes.RESET_TEI_ID)();

export const fetchTei = (payload: IdSuite) =>
actionCreator(enrollmentPageActionTypes.FETCH_TEI)(payload);

export const verifyFetchTeiSuccess = (payload: { ...IdSuite, teiDisplayName: string, tetId: string }) =>
actionCreator(enrollmentPageActionTypes.VERIFY_FETCH_TEI_SUCCESS)(payload);

export const successfulFetchingEnrollmentPageInformationFromUrl = ({ teiDisplayName, tetId }: Object) =>
actionCreator(enrollmentPageActionTypes.INFORMATION_SUCCESS_FETCH)(
{ teiDisplayName, tetId });
export const fetchTeiSuccess = (payload: { ...IdSuite, teiDisplayName: string, tetId: string }) =>
actionCreator(enrollmentPageActionTypes.FETCH_TEI_SUCCESS)(payload);

export const fetchTeiError = (teiId: string) =>
actionCreator(enrollmentPageActionTypes.FETCH_TEI_ERROR)({ teiId });

// programId
export const changedProgramId = (payload: IdSuite) =>
actionCreator(enrollmentPageActionTypes.PROCESS_PROGRAM_ID)(payload);

export const commitTrackerProgramId = (programId: string) =>
actionCreator(enrollmentPageActionTypes.COMMIT_TRACKER_PROGRAM_ID)({ programId });

export const commitNonTrackerProgramId = (programId: string) =>
actionCreator(enrollmentPageActionTypes.COMMIT_NON_TRACKER_PROGRAM_ID)({ programId });

export const programIdError = (programId: string) =>
actionCreator(enrollmentPageActionTypes.PROGRAM_ID_ERROR)({ programId });

// enrollments
export const fetchEnrollments = () =>
actionCreator(enrollmentPageActionTypes.ENROLLMENTS_FETCH)();
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENTS)();

export const verifyFetchedEnrollments = ({ teiId, programId, action }: Object) =>
actionCreator(enrollmentPageActionTypes.VERIFY_FETCHED_ENROLLMENTS)({ teiId, programId, action });

export const updateEnrollmentAccessLevel = ({ programId, accessLevel }: { programId: string, accessLevel: string }) =>
actionCreator(enrollmentPageActionTypes.ENROLLMENTS_ERROR_FETCH)({ programId, accessLevel });
export const fetchEnrollmentsError = ({ accessLevel }: { accessLevel: string }) =>
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENTS_ERROR)({ accessLevel });

export const saveEnrollments = ({ programId, enrollments }: any) =>
actionCreator(enrollmentPageActionTypes.ENROLLMENTS_SUCCESS_FETCH)({ programId, enrollments });
export const saveEnrollments = ({ enrollments }: any) =>
actionCreator(enrollmentPageActionTypes.FETCH_ENROLLMENTS_SUCCESS)({ enrollments });

export const openEnrollmentPage = ({ programId, orgUnitId, teiId, enrollmentId }: Object) =>
actionCreator(enrollmentPageActionTypes.PAGE_OPEN)({ programId, orgUnitId, teiId, enrollmentId });
// Page status
export const showDefaultViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.DEFAULT_VIEW)();

export const showLoadingViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.LOADING_VIEW)();

export const showMissingMessageViewOnEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.MISSING_MESSAGE_VIEW)();

export const showErrorViewOnEnrollmentPage = ({ error }: { error: string }) =>
actionCreator(enrollmentPageActionTypes.ERROR_VIEW)({ error });

export const cleanEnrollmentPage = () =>
actionCreator(enrollmentPageActionTypes.PAGE_CLEAN)();
export const clearErrorView = () =>
actionCreator(enrollmentPageActionTypes.CLEAR_ERROR_VIEW)();

// Mutations
export const deleteEnrollment = ({ enrollmentId }: { enrollmentId: string }) =>
actionCreator(enrollmentPageActionTypes.DELETE_ENROLLMENT)({
enrollmentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const enrollmentPageStatuses = {
MISSING_SELECTIONS: 'MISSING_SELECTIONS',
};

export const selectionStatus = {
READY: 'READY',
ERROR: 'ERROR',
LOADING: 'LOADING',
};

export const enrollmentAccessLevels = {
FULL_ACCESS: 'FULL_ACCESS',
LIMITED_ACCESS: 'LIMITED_ACCESS',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import React, { useEffect, useMemo } from 'react';
import type { ComponentType } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { EnrollmentPageComponent } from './EnrollmentPage.component';
import type { EnrollmentPageStatus } from './EnrollmentPage.types';
import {
cleanEnrollmentPage,
fetchEnrollmentPageInformation,
fetchEnrollments,
updateEnrollmentAccessLevel,
openEnrollmentPage,
closeEnrollmentPage,
changedEnrollmentId,
changedTeiId,
changedProgramId,
resetTeiId,
showDefaultViewOnEnrollmentPage,
showMissingMessageViewOnEnrollmentPage,
showLoadingViewOnEnrollmentPage,
} from './EnrollmentPage.actions';
import { scopeTypes } from '../../../metaData/helpers/constants';
import { useScopeInfo } from '../../../hooks/useScopeInfo';
import { useEnrollmentInfo } from './useEnrollmentInfo';
import { enrollmentPageStatuses, enrollmentAccessLevels } from './EnrollmentPage.constants';
import { enrollmentPageStatuses } from './EnrollmentPage.constants';
import { getScopeInfo } from '../../../metaData';
import {
buildEnrollmentsAsOptions,
Expand All @@ -30,7 +31,10 @@ const useComponentLifecycle = () => {

const { scopeType } = useScopeInfo(programId);
const { setEnrollmentId } = useSetEnrollmentId();
const { enrollmentAccessLevel, programId: enrollmentProgramId } = useSelector(({ enrollmentPage }) => enrollmentPage);
const {
enrollmentPageStatus,
programId: enrollmentProgramId,
} = useSelector(({ enrollmentPage }) => enrollmentPage);

const { programHasEnrollments, enrollmentsOnProgramContainEnrollmentId, autoEnrollmentId } = useEnrollmentInfo(enrollmentId, programId, teiId);
useEffect(() => {
Expand All @@ -39,7 +43,7 @@ const useComponentLifecycle = () => {
setEnrollmentId({ enrollmentId: autoEnrollmentId, shouldReplaceHistory: true });
} else if (selectedProgramIsTracker && programHasEnrollments && enrollmentsOnProgramContainEnrollmentId) {
dispatch(showDefaultViewOnEnrollmentPage());
} else if (programId === enrollmentProgramId) {
} else if (programId === enrollmentProgramId && enrollmentPageStatus !== enrollmentPageStatuses.LOADING) {
dispatch(showMissingMessageViewOnEnrollmentPage());
} else {
dispatch(showLoadingViewOnEnrollmentPage());
Expand All @@ -54,18 +58,18 @@ const useComponentLifecycle = () => {
scopeType,
enrollmentId,
autoEnrollmentId,
enrollmentAccessLevel,
enrollmentPageStatus,
enrollmentProgramId,
]);

useEffect(() => () => dispatch(cleanEnrollmentPage()), [dispatch, teiId]);
useEffect(() => () => dispatch(closeEnrollmentPage()), [dispatch]);
};

// dirty fix for scenarios where you deselect the program.
// This should be removed as part of fixing the url sync issue, https://jira.dhis2.org/browse/TECH-580
const useComputedEnrollmentPageStatus = () => {
const enrollmentPageStatus: EnrollmentPageStatus =
useSelector(({ enrollmentPage }) => enrollmentPage.enrollmentPageStatus);
const {
enrollmentPageStatus,
programId: reduxProgramId,
} = useSelector(({ enrollmentPage }) => enrollmentPage);

const { teiId, programId, enrollmentId } = useLocationQuery();
const { scopeType } = useScopeInfo(programId);
Expand All @@ -75,7 +79,7 @@ const useComputedEnrollmentPageStatus = () => {
return enrollmentPageStatuses.MISSING_SELECTIONS;
}
if (enrollmentPageStatus === enrollmentPageStatuses.DEFAULT &&
!(programId && teiId && enrollmentId)) {
!(programId && teiId && enrollmentId && programId === reduxProgramId)) {
return enrollmentPageStatuses.LOADING;
}
return enrollmentPageStatus;
Expand All @@ -85,6 +89,7 @@ const useComputedEnrollmentPageStatus = () => {
enrollmentId,
teiId,
programId,
reduxProgramId,
]);
};

Expand All @@ -98,25 +103,12 @@ export const EnrollmentPage: ComponentType<{||}> = () => {
const enrollmentsAsOptions = buildEnrollmentsAsOptions(enrollments, programId);

useEffect(() => {
dispatch(fetchEnrollmentPageInformation());
},
[
dispatch,
teiId,
]);
dispatch(openEnrollmentPage());
}, [dispatch]);

useEffect(() => {
programId ?
dispatch(fetchEnrollments()) :
dispatch(updateEnrollmentAccessLevel({
programId,
accessLevel: enrollmentAccessLevels.UNKNOWN_ACCESS,
}));
},
[
dispatch,
programId,
]);
useEffect(() => { dispatch(changedEnrollmentId(enrollmentId)); }, [dispatch, enrollmentId]);
useEffect(() => { dispatch(teiId ? changedTeiId({ teiId }) : resetTeiId()); }, [dispatch, teiId]);
useEffect(() => { dispatch(changedProgramId({ programId })); }, [dispatch, programId]);

const error: boolean =
useSelector(({ activePage }) => activePage.selectionsError && activePage.selectionsError.error);
Expand Down
Loading
Loading